From 53a73fc1d14a589bafb85881fe970b370246e0b3 Mon Sep 17 00:00:00 2001 From: emv Date: Mon, 19 Mar 2018 07:50:06 +0300 Subject: [PATCH] 0029683: Add functionality to make the TopoDS_Shape periodic in 3D space Implementation of the new class *BOPAlgo_MakePeriodic* for making the shape periodic in 3D space. Periodicity of the shape means that the shape can be repeated in any periodic direction any number of times without creation of the new geometry or splits. The idea of this algorithm is to make the shape look similarly on the opposite sides or on the period bounds of periodic directions. It does not mean that the opposite sides of the shape will be mirrored. It just means the the opposite sides of the shape should be split by each other and obtain the same geometry on opposite sides. Such approach will allow repeating the shape, i.e. translating the copy of a shape on the period, without creation of new geometry because there will be no coinciding parts of different dimension. Draw commands for the new algorithm: * makeperiodic - makes the shape periodic in required directions; * repeatshape - repeats the periodic shape in requested periodic direction; * periodictwins - returns the periodic twins for the shape; * clearrepetitions - clears all previous repetitions of the periodic shape. Documentation & test cases for the algorithm. --- .../draw_test_harness/draw_test_harness.md | 63 +- .../modeling_algos_mkperiodic_im001.png | Bin 0 -> 15046 bytes .../modeling_algos_mkperiodic_im002.png | Bin 0 -> 15803 bytes .../modeling_algos_mkperiodic_im003.png | Bin 0 -> 23068 bytes .../modeling_algos_mkperiodic_im004.png | Bin 0 -> 22704 bytes .../modeling_algos_mkperiodic_im005.png | Bin 0 -> 13531 bytes .../modeling_algos_mkperiodic_im006.png | Bin 0 -> 23179 bytes .../modeling_algos/modeling_algos.md | 165 +++++ src/BOPAlgo/BOPAlgo.msg | 12 + src/BOPAlgo/BOPAlgo_Alerts.hxx | 14 +- src/BOPAlgo/BOPAlgo_BOPAlgo_msg.pxx | 14 +- src/BOPAlgo/BOPAlgo_Builder_1.cxx | 53 +- src/BOPAlgo/BOPAlgo_MakePeriodic.cxx | 617 ++++++++++++++++++ src/BOPAlgo/BOPAlgo_MakePeriodic.hxx | 603 +++++++++++++++++ src/BOPAlgo/BOPAlgo_PaveFiller.cxx | 16 - src/BOPAlgo/BOPAlgo_PaveFiller.hxx | 20 +- src/BOPAlgo/BOPAlgo_Splitter.cxx | 30 + src/BOPAlgo/BOPAlgo_Splitter.hxx | 6 + src/BOPAlgo/FILES | 2 + src/BOPTest/BOPTest.cxx | 1 + src/BOPTest/BOPTest.hxx | 2 + src/BOPTest/BOPTest_PeriodicityCommands.cxx | 293 +++++++++ src/BOPTest/FILES | 1 + tests/boolean/grids.list | 3 +- tests/boolean/periodicity/A1 | 15 + tests/boolean/periodicity/A2 | 80 +++ tests/boolean/periodicity/A3 | 14 + tests/boolean/periodicity/A4 | 13 + tests/boolean/periodicity/A5 | 39 ++ tests/boolean/periodicity/A6 | 62 ++ tests/bugs/modalg_7/bug29502 | 5 +- 31 files changed, 2093 insertions(+), 50 deletions(-) create mode 100644 dox/user_guides/modeling_algos/images/modeling_algos_mkperiodic_im001.png create mode 100644 dox/user_guides/modeling_algos/images/modeling_algos_mkperiodic_im002.png create mode 100644 dox/user_guides/modeling_algos/images/modeling_algos_mkperiodic_im003.png create mode 100644 dox/user_guides/modeling_algos/images/modeling_algos_mkperiodic_im004.png create mode 100644 dox/user_guides/modeling_algos/images/modeling_algos_mkperiodic_im005.png create mode 100644 dox/user_guides/modeling_algos/images/modeling_algos_mkperiodic_im006.png create mode 100644 src/BOPAlgo/BOPAlgo_MakePeriodic.cxx create mode 100644 src/BOPAlgo/BOPAlgo_MakePeriodic.hxx create mode 100644 src/BOPTest/BOPTest_PeriodicityCommands.cxx create mode 100644 tests/boolean/periodicity/A1 create mode 100644 tests/boolean/periodicity/A2 create mode 100644 tests/boolean/periodicity/A3 create mode 100644 tests/boolean/periodicity/A4 create mode 100644 tests/boolean/periodicity/A5 create mode 100644 tests/boolean/periodicity/A6 diff --git a/dox/user_guides/draw_test_harness/draw_test_harness.md b/dox/user_guides/draw_test_harness/draw_test_harness.md index 241da6147f..fdabe65033 100644 --- a/dox/user_guides/draw_test_harness/draw_test_harness.md +++ b/dox/user_guides/draw_test_harness/draw_test_harness.md @@ -5860,6 +5860,7 @@ The following topics are covered in the eight sections of this chapter: * Topological operations, or booleans. * Drafting and blending. * Defeaturing. + * Making shapes periodic in 3D space. * Analysis of shapes. @@ -7191,6 +7192,66 @@ parallel - enables the parallel processing mode. ~~~~ +@subsection occt_draw_makeperiodic 3D Model Periodicity + +Draw module for @ref occt_modalg_makeperiodic "making the shape periodic" includes the following commands: +* **makeperiodic** - makes the shape periodic in required directions; +* **repeatshape** - repeats the periodic shape in requested periodic direction; +* **periodictwins** - returns the periodic twins for the shape; +* **clearrepetitions** - clears all previous repetitions of the periodic shape. + +@subsubsection occt_draw_makeperiodic_makeperiodic makeperiodic + +The command makes the shape periodic in the required directions with the required period. +If trimming is given it trims the shape to fit the requested period. + +Syntax: +~~~~ +makeperiodic result shape [-x/y/z period [-trim first]] + +Where: +result - resulting periodic shape; +shape - input shape to make it periodic: +-x/y/z period - option to make the shape periodic in X, Y or Z direction with the given period; +-trim first - option to trim the shape to fit the required period, starting the period in first. +~~~~ + +@subsubsection occt_draw_makeperiodic_repeatshape repeatshape + +The command repeats the periodic shape in periodic direction requested number of time. +The result contains the all the repeated shapes glued together. +The command should be called after **makeperiodic** command. + +Syntax: +~~~~ +repeatshape result -x/y/z times + +Where: +result - resulting shape; +-x/y/z times - direction for repetition and number of repetitions (negative number of times means the repetition in negative direction). +~~~~ + +@subsubsection occt_draw_makeperiodic_periodictwins periodictwins + +For the given shape the command returns the identical shapes located on the opposite sides of the periodic direction. +All periodic twins should have the same geometry. +The command should be called after **makeperiodic** command. + +Syntax: +~~~~ +periodictwins twins shape + +Where: +twins - periodic twins for the given shape +shape - shape to find the twins for +~~~~ + +@subsubsection occt_draw_makeperiodic_clearrepetitions clearrepetitions + +The command clears all previous repetitions of the periodic shape allowing to start the repetitions over. +No arguments are needed for the command. + + @subsection occt_draw_7_9 Analysis of topology and geometry Analysis of shapes includes commands to compute length, area, volumes and inertial properties, as well as to compute some aspects impacting shape validity. @@ -8551,7 +8612,7 @@ Where: @subsubsection occt_draw_bop_options_warn Drawing warning shapes -**bdrawwarnshapes** command enables/disables drawing of waring shapes of BOP algorithms. +**bdrawwarnshapes** command enables/disables drawing of warning shapes of BOP algorithms. Syntax: ~~~~ diff --git a/dox/user_guides/modeling_algos/images/modeling_algos_mkperiodic_im001.png b/dox/user_guides/modeling_algos/images/modeling_algos_mkperiodic_im001.png new file mode 100644 index 0000000000000000000000000000000000000000..b7fea40efd780df160dafcf19922168c0fc34de5 GIT binary patch literal 15046 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 literal 0 HcmV?d00001 diff --git a/dox/user_guides/modeling_algos/images/modeling_algos_mkperiodic_im002.png b/dox/user_guides/modeling_algos/images/modeling_algos_mkperiodic_im002.png new file mode 100644 index 0000000000000000000000000000000000000000..23fa2114a2f8635fac00e698db2cfb4e1b902b1e GIT binary patch literal 15803 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 Wx$)r-py67<{@lg0C1|E}7X#IkU8o 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 Rmf_2nk{{R}p#;^bY literal 0 HcmV?d00001 diff --git a/dox/user_guides/modeling_algos/images/modeling_algos_mkperiodic_im005.png b/dox/user_guides/modeling_algos/images/modeling_algos_mkperiodic_im005.png new file mode 100644 index 0000000000000000000000000000000000000000..b68d0c3b3fc1510ccd359cb3ae05563eaaa63c19 GIT binary patch literal 13531 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/user_guides/modeling_algos/images/modeling_algos_rf_im029.png,"Result",220} + + +@section occt_modalg_makeperiodic 3D Model Periodicity + +Open CASCADE Technology provides tools for making an arbitrary 3D model (or just shape) periodic in 3D space in the specified directions. + +Periodicity of the shape means that the shape can be repeated in any periodic direction any number of times without creation of the new geometry or splits. +The idea of this algorithm is to make the shape look similarly on the opposite sides or on the period bounds of periodic directions. +It does not mean that the opposite sides of the shape will be mirrored. It just means that the opposite sides of the shape should be split by each other and obtain the same geometry on opposite sides. +Such approach will allow repeating the shape, i.e. translating the copy of a shape on the period, without creation of new geometry because there will be no coinciding parts of different dimension. + +For better understanding of what periodicity means lets create a simple prism and make it periodic. +The following draw script creates the L-shape prism with extensions 10x5x10: +~~~~ +polyline p 0 0 0 0 0 10 5 0 10 5 0 5 10 0 5 10 0 0 0 0 0 +mkplane f p +prism s f 0 5 0 +~~~~ +@figure{/user_guides/modeling_algos/images/modeling_algos_mkperiodic_im001.png,"Shape to make periodic",220} + +Making this shape periodic in X, Y and Z directions with the periods matching the shape's extensions should make the splits of negative X and Z sides of the shape. The shape is already similar on opposite sides of Y directions, thus no new splits is expected. +Here is the shape after making it periodic: +@figure{/user_guides/modeling_algos/images/modeling_algos_mkperiodic_im002.png,"Periodic shape",220} +And here is the repetition of the shape once in X and Z directions: +@figure{/user_guides/modeling_algos/images/modeling_algos_mkperiodic_im003.png,"Repeated shape",220} + +The OCCT Shape Periodicity tools also allows making the shape periodic with the period not matching the shape's extensions. Let's make the shape periodic with 11, 6 and 11 for X, Y, Z periods accordingly. +Such values of periods mean that there will be a gap between repeated shapes, and since during repetition the opposite sides do not touch the shape will not be split at all. +Here is the repetition of the shape once in X and Z directions: +@figure{/user_guides/modeling_algos/images/modeling_algos_mkperiodic_im004.png,"Repeated shape",220} +As expected, the shape is not split and the repeated elements do not touch. + +If necessary the algorithm will trim the shape to fit into the requested period by splitting it with the planes limiting the shape's requested periods. +E.g. let's make the L-shape periodic only in X direction with the period 2 starting at X parameter 4: +@figure{/user_guides/modeling_algos/images/modeling_algos_mkperiodic_im005.png,"Periodic trimmed shape",220} + +@subsection occt_modalg_makeperiodic_how_it_works How the shape is made periodic + +For making the shape periodic in certain direction the algorithm performs the following steps: +* Creates the copy of the shape and moves it on the period into negative side of the requested direction; +* Splits the negative side of the shape by the moved copy, ensuring copying of the geometry from positive side to negative; +* Creates the copy of the shape (with already split negative side) and moves it on the period into the positive side of the requested direction; +* Splits the positive side of the shape by the moved copy, ensuring copying of the geometry from negative side to positive. + +Repeated copying of the geometry ensures that the corner edges of the periodic shape will have the same geometry on opposite sides of all periodic directions. + +Thus, in the periodic shape the geometry from positive side of the shape is always copied on the negative side of periodic directions. + +@subsection occt_modalg_makeperiodic_association Opposite shapes association + +The algorithm also associates the identical (or twin) shapes located on the opposite sides of the periodic shape. By the construction, the twin shapes should always have the same geometry and distanced from each other on the period. +It is possible that the shape does not have any twins. It means that when repeating this shape will not touch the opposite side of the shape. In the example when the periods of the shape are grater than its extensions, non of the sub-shapes has a twin. + +@subsection occt_modalg_makeperiodic_repetition Periodic shape repetition + +The algorithm also provides the methods to repeat the periodic shape in periodic directions. To repeat shape the algorithm makes the requested number of copies of the shape and translates them one by one on the time * period value. +After all copies are made and translated they are glued to have valid shape. +The subsequent repetitions are performed on the repeated shape, thus e.g. repeating the shape two times in any periodic direction will create result containing three shapes (original plus two copies). +Single subsequent repetition in any direction will result already in 6 shapes. + +The repetitions can be cleared and started over. + +@subsection occt_modalg_makeperiodic_history History support + +The algorithm supports the history of shapes modifications, thus it is possible to track how the shapes have been changed to make it periodic and what new shapes have been created during repetitions. +Both split history and history of periodic shape repetition are available here. Note, that all repeated shapes are stored as generated into the history. + + +*BRepTools_History* is used for history support. + +@subsection occt_modalg_makeperiodic_errors Errors/Warnings + +The algorithm supports the Error/Warning reporting system which allows obtaining the extended overview of the errors and warning occurred during the operation. +As soon as any error appears the algorithm stops working. The warnings allow continuing the job, informing the user that something went wrong. +The algorithm returns the following alerts: +* *BOPAlgo_AlertNoPeriodicityRequired* - Error alert is given if no periodicity has been requested in any direction; +* *BOPAlgo_AlertUnableToTrim* - Error alert is given if the trimming of the shape for fitting it into requested period has failed; +* *BOPAlgo_AlertUnableToMakeIdentical* - Error alert is given if splitting of the shape by its moved copies has failed; +* *BOPAlgo_AlertUnableToRepeat* - Warning alert is given if the gluing of the repeated shapes has failed. + +For more information on the error/warning reporting system please see the chapter @ref occt_algorithms_ers "Errors and warnings reporting system" of Boolean operations user guide. + +@subsection occt_modalg_makeperiodic_usage Usage + +The algorithm is implemented in the class *BOPAlgo_MakePeriodic*. +Here is the example of its usage on the API level: +~~~~ +TopoDS_Shape aShape = ...; // The shape to make periodic +Standard_Boolean bMakeXPeriodic = ...; // Flag for making or not the shape periodic in X direction +Standard_Real aXPeriod = ...; // X period for the shape +Standard_Boolean isXTrimmed = ...; // Flag defining whether it is necessary to trimming + // the shape to fit to X period +Standard_Real aXFirst = ...; // Start of the X period + // (really necessary only if the trimming is requested) +Standard_Boolean bRunParallel = ...; // Parallel processing mode or single + +BOPAlgo_MakePeriodic aPeriodicityMaker; // Periodicity maker +aPeriodicityMaker.SetShape(aShape); // Set the shape +aPeriodicityMaker.MakeXPeriodic(bMakePeriodic, aXPeriod); // Making the shape periodic in X direction +aPeriodicityMaker.SetTrimmed(isXTrimmed, aXFirst); // Trim the shape to fit X period +aPeriodicityMaker.SetRunParallel(bRunParallel); // Set the parallel processing mode +aPeriodicityMaker.Perform(); // Performing the operation + +if (aPeriodicityMaker.HasErrors()) // Check for the errors +{ + // errors treatment + Standard_SStream aSStream; + aPeriodicityMaker.DumpErrors(aSStream); + return; +} +if (aPeriodicityMaker.HasWarnings()) // Check for the warnings +{ + // warnings treatment + Standard_SStream aSStream; + aPeriodicityMaker.DumpWarnings(aSStream); +} +const TopoDS_Shape& aPeriodicShape = aPeriodicityMaker.Shape(); // Result periodic shape + +aPeriodicityMaker.XRepeat(2); // Making repetitions +const TopoDS_Shape& aRepeat = aPeriodicityMaker.RepeatedShape(); // Getting the repeated shape +aPeriodicityMaker.ClearRepetitions(); // Clearing the repetitions +~~~~ + +Please note, that the class is based on the options class *BOPAlgo_Options*, which provides the following options for the algorithm: +* Error/Warning reporting system; +* Parallel processing mode. +The other options of the base class are not supported here and will have no effect. + +All the history information obtained during the operation is stored into *BRepTools_History* object and available through *History()* method: +~~~~ +// Get the history object +const Handle(BRepTools_History)& BOPAlgo_MakePeriodic::History(); +~~~~ + +For the usage of the MakePeriodic algorithm on the Draw level the following commands have been implemented: +* **makeperiodic** +* **repeatshape** +* **periodictwins** +* **clearrepetitions** + +For more details on the periodicity commands please refer the @ref occt_draw_makeperiodic "Periodicity commands" of the Draw test harness user guide. + +To track the history of a shape modification during MakePeriodic operation the @ref occt_draw_hist "standard history commands" can be used. + +To have possibility to access the error/warning shapes of the operation use the *bdrawwarnshapes* command before running the algorithm (see command usage in the @ref occt_algorithms_ers "Errors and warnings reporting system" of Boolean operations user guide). + +@subsection occt_modalg_makeperiodic_examples Examples + +Imagine that you need to make the drills in the plate on the same distance from each other. To model this process it is necessary to make a lot of cylinders (simulating the drills) and cut these cylinders from the plate. +With the periodicity tool, the process looks very simple: +~~~~ +# create plate 100 x 100 +box plate -50 -50 0 100 100 1 +# create a single drill with radius 1 +pcylinder drill 1 1 +# locate the drill in the left corner +ttranslate drill -48 -48 0 +# make the drill periodic with 4 as X and Y periods, so the distance between drills will be 2 +makeperiodic drill drill -x 4 -trim -50 -y 4 -trim -50 +# repeat the drill to fill the plate, in result we get net of 25 x 25 drills +repeatshape drills -x 24 -y 24 +# cut the drills from the plate +bcut result plate drills +~~~~ +@figure{/user_guides/modeling_algos/images/modeling_algos_mkperiodic_im006.png,"Plate with drills",220} diff --git a/src/BOPAlgo/BOPAlgo.msg b/src/BOPAlgo/BOPAlgo.msg index 4e5bd1cb20..abef3ccf3e 100644 --- a/src/BOPAlgo/BOPAlgo.msg +++ b/src/BOPAlgo/BOPAlgo.msg @@ -100,3 +100,15 @@ Unable to orient the shape correctly .BOPAlgo_AlertUnknownShape Shape is unknown for operation + +.BOPAlgo_AlertNoPeriodicityRequired +No periodicity has been requested for the shape + +.BOPAlgo_AlertUnableToTrim +Unable to trim the shape for making it periodic (BOP Common fails) + +.BOPAlgo_AlertUnableToMakeIdentical +Unable to make the shape to look identical on opposite sides (Splitter fails) + +.BOPAlgo_AlertUnableToRepeat +Unable to repeat the shape (Gluer fails) diff --git a/src/BOPAlgo/BOPAlgo_Alerts.hxx b/src/BOPAlgo/BOPAlgo_Alerts.hxx index 8274528c15..21960d824b 100644 --- a/src/BOPAlgo/BOPAlgo_Alerts.hxx +++ b/src/BOPAlgo/BOPAlgo_Alerts.hxx @@ -30,7 +30,7 @@ DEFINE_SIMPLE_ALERT(BOPAlgo_AlertBuilderFailed) //! The intersection of the arguments has failed DEFINE_SIMPLE_ALERT(BOPAlgo_AlertIntersectionFailed) -//! The type of Boolean Operation is not set +//! More than one argument is provided DEFINE_SIMPLE_ALERT(BOPAlgo_AlertMultipleArguments) //! The Pave Filler (the intersection tool) has not been created @@ -114,4 +114,16 @@ DEFINE_ALERT_WITH_SHAPE(BOPAlgo_AlertUnableToOrientTheShape) //! Shape is unknown for operation DEFINE_ALERT_WITH_SHAPE(BOPAlgo_AlertUnknownShape) +//! No periodicity has been requested for the shape +DEFINE_SIMPLE_ALERT(BOPAlgo_AlertNoPeriodicityRequired) + +//! Unable to trim the shape for making it periodic (BOP Common fails) +DEFINE_ALERT_WITH_SHAPE(BOPAlgo_AlertUnableToTrim) + +//! Unable to make the shape to look identical on opposite sides (Splitter fails) +DEFINE_ALERT_WITH_SHAPE(BOPAlgo_AlertUnableToMakeIdentical) + +//! Unable to repeat the shape (Gluer fails) +DEFINE_ALERT_WITH_SHAPE(BOPAlgo_AlertUnableToRepeat) + #endif // _BOPAlgo_Alerts_HeaderFile diff --git a/src/BOPAlgo/BOPAlgo_BOPAlgo_msg.pxx b/src/BOPAlgo/BOPAlgo_BOPAlgo_msg.pxx index 61ea072c91..1647b22f2b 100644 --- a/src/BOPAlgo/BOPAlgo_BOPAlgo_msg.pxx +++ b/src/BOPAlgo/BOPAlgo_BOPAlgo_msg.pxx @@ -102,4 +102,16 @@ static const char BOPAlgo_BOPAlgo_msg[] = "Unable to orient the shape correctly\n" "\n" ".BOPAlgo_AlertUnknownShape\n" - "Shape is unknown for operation\n"; + "Shape is unknown for operation\n" + "\n" + ".BOPAlgo_AlertNoPeriodicityRequired\n" + "No periodicity has been requested for the shape\n" + "\n" + ".BOPAlgo_AlertUnableToTrim\n" + "Unable to trim the shape for making it periodic (BOP Common fails)\n" + "\n" + ".BOPAlgo_AlertUnableToMakeIdentical\n" + "Unable to make the shape to look identical on opposite sides (Splitter fails)\n" + "\n" + ".BOPAlgo_AlertUnableToRepeat\n" + "Unable to repeat the shape (Gluer fails)\n"; diff --git a/src/BOPAlgo/BOPAlgo_Builder_1.cxx b/src/BOPAlgo/BOPAlgo_Builder_1.cxx index 97ec6c7846..90767d6982 100644 --- a/src/BOPAlgo/BOPAlgo_Builder_1.cxx +++ b/src/BOPAlgo/BOPAlgo_Builder_1.cxx @@ -112,32 +112,35 @@ void BOPAlgo_Builder::FillImagesVertices() //function : BuildResult //purpose : //======================================================================= - void BOPAlgo_Builder::BuildResult(const TopAbs_ShapeEnum theType) +void BOPAlgo_Builder::BuildResult(const TopAbs_ShapeEnum theType) { - TopAbs_ShapeEnum aType; - BRep_Builder aBB; - TopTools_MapOfShape aM; - TopTools_ListIteratorOfListOfShape aIt, aItIm; - // - aIt.Initialize(myArguments); - for (; aIt.More(); aIt.Next()) { - const TopoDS_Shape& aS=aIt.Value(); - aType=aS.ShapeType(); - if (aType==theType) { - if (myImages.IsBound(aS)){ - const TopTools_ListOfShape& aLSIm=myImages.Find(aS); - aItIm.Initialize(aLSIm); - for (; aItIm.More(); aItIm.Next()) { - const TopoDS_Shape& aSIm=aItIm.Value(); - if (aM.Add(aSIm)) { - aBB.Add(myShape, aSIm); - } - } - } - else { - if (aM.Add(aS)) { - aBB.Add(myShape, aS); - } + // Fence map + TopTools_MapOfShape aMFence; + // Iterate on all arguments of given type + // and add their images into result + TopTools_ListIteratorOfListOfShape aItA(myArguments); + for (; aItA.More(); aItA.Next()) + { + const TopoDS_Shape& aS = aItA.Value(); + if (aS.ShapeType() != theType) + continue; + // Get images + const TopTools_ListOfShape* pLSIm = myImages.Seek(aS); + if (!pLSIm) + { + // No images -> add the argument shape itself into result + if (aMFence.Add(aS)) + BRep_Builder().Add(myShape, aS); + } + else + { + // Add images of the argument shape into result + TopTools_ListIteratorOfListOfShape aItIm(*pLSIm); + for (; aItIm.More(); aItIm.Next()) + { + const TopoDS_Shape& aSIm = aItIm.Value(); + if (aMFence.Add(aSIm)) + BRep_Builder().Add(myShape, aSIm); } } } diff --git a/src/BOPAlgo/BOPAlgo_MakePeriodic.cxx b/src/BOPAlgo/BOPAlgo_MakePeriodic.cxx new file mode 100644 index 0000000000..1b32d93921 --- /dev/null +++ b/src/BOPAlgo/BOPAlgo_MakePeriodic.cxx @@ -0,0 +1,617 @@ +// Created on: 2018-03-16 +// Created by: Eugeny MALTCHIKOV +// Copyright (c) 2018 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include + +#include + +#include + +#include +#include + +#include +#include + +#include + +#include + +#include + +#include + +#include + +#include +#include + +#include + +// Periodic/Trim/Repeat directions +static const gp_Dir MY_DIRECTIONS[3] = { gp::DX(), + gp::DY(), + gp::DZ() }; + +//======================================================================= +//function : Perform +//purpose : Performs the operation +//======================================================================= +void BOPAlgo_MakePeriodic::Perform() +{ + // Check the validity of input data + CheckData(); + if (HasErrors()) + return; + + // Trim the shape to fit to the required period in + // required periodic directions + Trim(); + if (HasErrors()) + return; + + // Make the shape identical on the opposite sides in + // required periodic directions + MakeIdentical(); + if (HasErrors()) + return; +} + +//======================================================================= +//function : CheckData +//purpose : Checks the validity of input data +//======================================================================= +void BOPAlgo_MakePeriodic::CheckData() +{ + if ( (!IsXPeriodic() || XPeriod() < Precision::Confusion()) + && (!IsYPeriodic() || YPeriod() < Precision::Confusion()) + && (!IsZPeriodic() || ZPeriod() < Precision::Confusion())) + { + // Add error informing the user that no periodicity is required + // or no valid period is set. + + AddError(new BOPAlgo_AlertNoPeriodicityRequired()); + return; + } +} + +//======================================================================= +//function : AddToShape +//purpose : Adds the shape to the shape +//======================================================================= +static void AddToShape(const TopoDS_Shape& theWhat, + TopoDS_Shape& theWhere) +{ + if (theWhere.IsNull()) + BRep_Builder().MakeCompound(TopoDS::Compound(theWhere)); + BRep_Builder().Add(theWhere, theWhat); +} +//======================================================================= +//function : AddToShape +//purpose : Adds the shape in the list to the shape +//======================================================================= +static void AddToShape(const TopTools_ListOfShape& theLWhat, + TopoDS_Shape& theWhere) +{ + TopTools_ListIteratorOfListOfShape it(theLWhat); + for (; it.More(); it.Next()) + AddToShape(it.Value(), theWhere); +} + +//======================================================================= +//function : Trim +//purpose : Make the trim of the shape to fit to the periodic bounds. +//======================================================================= +void BOPAlgo_MakePeriodic::Trim() +{ + // Check if trim is required at all + if (IsInputXTrimmed() && + IsInputYTrimmed() && + IsInputZTrimmed()) + return; + + // Compute bounding box for the shape to use it as a starting + // volume for trimming. If required, the volume will be modified + // to the requested trimming size in requested directions. + Bnd_Box aBox; + BRepBndLib::Add(myInputShape, aBox); + // Enlarge box to avoid overlapping with the shape + aBox.Enlarge(0.1 * sqrt(aBox.SquareExtent())); + + // Get Corner points of the bounding box + gp_Pnt aPMin = aBox.CornerMin(); + gp_Pnt aPMax = aBox.CornerMax(); + + // Update corner points according to the requested trim parameters + for (Standard_Integer i = 0; i < 3; ++i) + { + if (IsInputTrimmed(i)) + continue; + + aPMin.SetCoord(i + 1, PeriodFirst(i)); + aPMax.SetCoord(i + 1, PeriodFirst(i) + Period(i)); + } + + // Build Trimming solid using corner points + BRepPrimAPI_MakeBox aMBox(aPMin, aPMax); + const TopoDS_Shape& aTrimBox = aMBox.Solid(); + + // Perform trimming of the shape by solid + BRepAlgoAPI_Common aCommon; + // Set Object + TopTools_ListOfShape anObj; + anObj.Append(myInputShape); + aCommon.SetArguments(anObj); + // Set Tool + TopTools_ListOfShape aTool; + aTool.Append(aTrimBox); + aCommon.SetTools(aTool); + // Set the parallel processing mode + aCommon.SetRunParallel(myRunParallel); + // Build + aCommon.Build(); + if (aCommon.HasErrors()) + { + // Unable to trim the shape + // Merge errors from Common operation + myReport->Merge(aCommon.GetReport()); + // Add new error saving the shapes for analysis + TopoDS_Compound aWS; + AddToShape(myInputShape, aWS); + AddToShape(aTrimBox, aWS); + AddError(new BOPAlgo_AlertUnableToTrim(aWS)); + return; + } + // Get the trimmed shape + myShape = aCommon.Shape(); + // Fill the History for the object only + mySplitHistory = new BRepTools_History(); + mySplitHistory->Merge(anObj, aCommon); +} + +//======================================================================= +//function : MakeIdentical +//purpose : Make the shape look the same on the opposite sides in the +// required periodic directions. +//======================================================================= +void BOPAlgo_MakePeriodic::MakeIdentical() +{ + if (myShape.IsNull()) + myShape = myInputShape; + + if (mySplitHistory.IsNull()) + mySplitHistory = new BRepTools_History; + + // Split the negative side of the shape with the geometry + // located on the positive side + SplitNegative(); + if (HasErrors()) + return; + + // Split the positive side of the shape with the geometry + // located on the negative side. + // Make sure that the opposite sides have identical geometries. + // Make associations between identical opposite shapes. + SplitPositive(); + + myHistory = new BRepTools_History(); + myHistory->Merge(mySplitHistory); +} + +//======================================================================= +//function : SplitNegative +//purpose : Split the negative side of the shape with the geometry +// located on the positive side. +//======================================================================= +void BOPAlgo_MakePeriodic::SplitNegative() +{ + // Copy geometry from positive side of the shape to the negative first. + // So, translate the shape in negative periodic directions only. + // + // To avoid conflicts when copying geometries from positive periodic sides + // perform split of each periodic side in a separate operation. + for (Standard_Integer i = 0; i < 3; ++i) + { + if (!IsPeriodic(i)) + continue; + + // Translate the shape to the negative side + gp_Trsf aNegTrsf; + aNegTrsf.SetTranslationPart(Period(i) * MY_DIRECTIONS[i].Reversed()); + BRepBuilderAPI_Transform aNegT(myShape, aNegTrsf, Standard_False); + + // Split the negative side of the shape. + TopTools_ListOfShape aTools; + aTools.Append(aNegT.Shape()); + SplitShape(aTools, mySplitHistory); + } +} + +//======================================================================= +//function : AddTwin +//purpose : Associates the shape with the shape in the map. +//======================================================================= +static void AddTwin(const TopoDS_Shape& theS, + const TopoDS_Shape& theTwin, + TopTools_DataMapOfShapeListOfShape& theMap) +{ + TopTools_ListOfShape *aTwins = theMap.ChangeSeek(theS); + if (!aTwins) + { + theMap.Bound(theS, TopTools_ListOfShape())->Append(theTwin); + return; + } + + // Check if the twin shape is not yet present in the list + TopTools_ListIteratorOfListOfShape itLT(*aTwins); + for (; itLT.More(); itLT.Next()) + { + if (theTwin.IsSame(itLT.Value())) + break; + } + + if (!itLT.More()) + aTwins->Append(theTwin); +} + +//======================================================================= +//function : SplitPositive +//purpose : Split the positive side of the shape with the geometry of the +// negative side. Associate the identical opposite sub-shapes. +//======================================================================= +void BOPAlgo_MakePeriodic::SplitPositive() +{ + // Prepare map of the sub-shapes of the input shape to make + // associations of the opposite shapes + TopTools_IndexedMapOfShape aSubShapesMap; + TopExp::MapShapes(myShape, aSubShapesMap); + const Standard_Integer aNbS = aSubShapesMap.Extent(); + + // Translate the shape to the positive periodic directions to make the + // shapes look identical on the opposite sides. + TopTools_ListOfShape aTools; + + // Remember the history of shapes translation + TopTools_IndexedDataMapOfShapeListOfShape aTranslationHistMap; + + // Make translations for all periodic directions + for (Standard_Integer i = 0; i < 3; ++i) + { + if (!IsPeriodic(i)) + continue; + + // Translate the shape to the positive side + gp_Trsf aPosTrsf; + aPosTrsf.SetTranslationPart(Period(i) * MY_DIRECTIONS[i]); + BRepBuilderAPI_Transform aTranslator(myShape, aPosTrsf, Standard_False); + aTools.Append(aTranslator.Shape()); + + // Fill the translation history map + for (Standard_Integer j = 1; j <= aNbS; ++j) + { + const TopoDS_Shape& aS = aSubShapesMap(j); + if (BRepTools_History::IsSupportedType(aS)) + { + const TopTools_ListOfShape& aSM = aTranslator.Modified(aS); + TopTools_ListOfShape* pTS = aTranslationHistMap.ChangeSeek(aS); + if (!pTS) + pTS = &aTranslationHistMap(aTranslationHistMap.Add(aS, TopTools_ListOfShape())); + pTS->Append(aSM.First()); + } + } + } + + // Keep the split shape history and history of tools modifications + // during the split for making association of the opposite identical shapes + Handle(BRepTools_History) aSplitShapeHist = new BRepTools_History, + aSplitToolsHist = new BRepTools_History; + // Split the positive side of the shape + SplitShape(aTools, aSplitShapeHist, aSplitToolsHist); + if (HasErrors()) + return; + + mySplitHistory->Merge(aSplitShapeHist); + + // Make associations between identical opposite sub-shapes + const Standard_Integer aNbSH = aTranslationHistMap.Extent(); + for (Standard_Integer i = 1; i <= aNbSH; ++i) + { + const TopoDS_Shape* pS = &aTranslationHistMap.FindKey(i); + const TopTools_ListOfShape& aSIm = aSplitShapeHist->Modified(*pS); + if (aSIm.Extent() == 1) + pS = &aSIm.First(); + else if (aSIm.Extent() > 1) + continue; + + const TopTools_ListOfShape& aLTranslated = aTranslationHistMap(i); + + TopTools_ListIteratorOfListOfShape itLT(aLTranslated); + for (; itLT.More(); itLT.Next()) + { + const TopoDS_Shape& aT = itLT.Value(); + // Get shapes modifications during the split + const TopTools_ListOfShape& aTSplits = aSplitToolsHist->Modified(aT); + + // Associate the shapes to each other + TopTools_ListIteratorOfListOfShape itSp(aTSplits); + for (; itSp.More(); itSp.Next()) + { + const TopoDS_Shape& aSp = itSp.Value(); + AddTwin(*pS, aSp, myTwins); + AddTwin(aSp, *pS, myTwins); + } + } + } +} + +//======================================================================= +//function : SplitShape +//purpose : Splits the shape by the given tools +//======================================================================= +void BOPAlgo_MakePeriodic::SplitShape(const TopTools_ListOfShape& theTools, + Handle(BRepTools_History) theSplitShapeHistory, + Handle(BRepTools_History) theSplitToolsHistory) +{ + // Make sure that the geometry from the tools will be copied to the split + // shape. For that, the tool shapes should be given to the Boolean Operations + // algorithm before the shape itself. This will make all coinciding parts + // use the geometry of the first argument. + + // Intersection tool for passing ordered arguments + BOPAlgo_PaveFiller anIntersector; + anIntersector.SetArguments(theTools); + // Add the shape + anIntersector.AddArgument(myShape); + // Use gluing to speed-up intersections + anIntersector.SetGlue(BOPAlgo_GlueShift); + // Use safe input mode, to avoid reusing geometry of the shape + anIntersector.SetNonDestructive(Standard_True); + // Set parallel processing mode + anIntersector.SetRunParallel(myRunParallel); + // Perform Intersection of the arguments + anIntersector.Perform(); + // Check for the errors + if (anIntersector.HasErrors()) + { + // Unable to split the shape on opposite sides + // Copy the intersection errors + myReport->Merge(anIntersector.GetReport()); + // Add new error saving the shapes for analysis + TopoDS_Compound aWS; + AddToShape(theTools, aWS); + AddToShape(myShape, aWS); + AddError(new BOPAlgo_AlertUnableToMakeIdentical(aWS)); + return; + } + + // Perform the splitting of the shape with the precomputed intersection results + BRepAlgoAPI_Splitter aSplitter(anIntersector); + // Set Object + TopTools_ListOfShape anObj; + anObj.Append(myShape); + aSplitter.SetArguments(anObj); + // Set Tools + aSplitter.SetTools(theTools); + // Use Gluing + aSplitter.SetGlue(BOPAlgo_GlueShift); + // Set parallel processing mode + aSplitter.SetRunParallel(myRunParallel); + // Perform splitting + aSplitter.Build(); + // Check for the errors + if (aSplitter.HasErrors()) + { + // Unable to split the shape on opposite sides + // Copy the splitter errors + myReport->Merge(aSplitter.GetReport()); + // Add new error saving the shape for analysis + TopoDS_Compound aWS; + AddToShape(theTools, aWS); + AddToShape(myShape, aWS); + AddError(new BOPAlgo_AlertUnableToMakeIdentical(aWS)); + return; + } + // Get the split shape + myShape = aSplitter.Shape(); + // Remember the split history + if (!theSplitShapeHistory.IsNull()) + theSplitShapeHistory->Merge(anObj, aSplitter); + if (!theSplitToolsHistory.IsNull()) + theSplitToolsHistory->Merge(theTools, aSplitter); +} + +//======================================================================= +//function : RepeatShape +//purpose : Repeats the shape in the required periodic direction +//======================================================================= +const TopoDS_Shape& BOPAlgo_MakePeriodic::RepeatShape(const Standard_Integer theDir, + const Standard_Integer theTimes) +{ + if (myRepeatedShape.IsNull()) + myRepeatedShape = myShape; + + if (!IsPeriodic(theDir)) + return myRepeatedShape; + + if (theTimes == 0) + return myRepeatedShape; + + // Get the shape's period in the required direction + const Standard_Integer id = BOPAlgo_MakePeriodic::ToDirectionID(theDir); + if (myRepeatPeriod[id] < Precision::Confusion()) + myRepeatPeriod[id] = Period(id); + const Standard_Real aPeriod = myRepeatPeriod[id]; + + // Coefficient to define in which direction the repetition will be performed: + // theTimes is positive - in positive direction; + // theTimes is negative - in negative direction. + const Standard_Integer iDir = theTimes > 0 ? 1 : -1; + + // Create the translation history - all translated shapes will be + // created as Generated from the shape. + BRepTools_History aTranslationHistory; + TopTools_IndexedMapOfShape aSubShapesMap; + TopExp::MapShapes(myRepeatedShape, aSubShapesMap); + const Standard_Integer aNbS = aSubShapesMap.Extent(); + + // Add shapes for gluing + TopTools_ListOfShape aShapes; + // Add the shape itself + aShapes.Append(myRepeatedShape); + for (Standard_Integer i = 1; i <= aNbS; ++i) + { + const TopoDS_Shape& aS = aSubShapesMap(i); + if (BRepTools_History::IsSupportedType(aS)) + aTranslationHistory.AddGenerated(aS, aS); + } + + // Create translated copies of the shape + for (Standard_Integer i = 1; i <= Abs(theTimes); ++i) + { + gp_Trsf aTrsf; + aTrsf.SetTranslationPart(iDir * i * aPeriod * MY_DIRECTIONS[id]); + BRepBuilderAPI_Transform aTranslator(myRepeatedShape, aTrsf, Standard_False); + aShapes.Append(aTranslator.Shape()); + + // Fill the translation history + for (Standard_Integer j = 1; j <= aNbS; ++j) + { + const TopoDS_Shape& aS = aSubShapesMap(j); + if (BRepTools_History::IsSupportedType(aS)) + { + const TopTools_ListOfShape& aLT = aTranslator.Modified(aS); + aTranslationHistory.AddGenerated(aS, aLT.First()); + } + } + } + + // Update the history with the translation History + myHistory->Merge(aTranslationHistory); + + // Glue the translated shapes all together + BOPAlgo_Builder aGluer; + aGluer.SetArguments(aShapes); + // Avoid intersections of the sub-shapes + aGluer.SetGlue(BOPAlgo_GlueFull); + // Set parallel processing mode + aGluer.SetRunParallel(myRunParallel); + // Perform gluing + aGluer.Perform(); + if (aGluer.HasErrors()) + { + // Repetition in this direction is not possible + // Add warning saving the shapes for analysis + TopoDS_Compound aWS; + AddToShape(aShapes, aWS); + AddWarning(new BOPAlgo_AlertUnableToRepeat(aWS)); + return myRepeatedShape; + } + // Get glued shape + myRepeatedShape = aGluer.Shape(); + + // Update repetition period for the next repetitions + myRepeatPeriod[id] += Abs(theTimes) * myRepeatPeriod[id]; + + // Update history with the Gluing history + BRepTools_History aGluingHistory(aShapes, aGluer); + myHistory->Merge(aGluingHistory); + + // Update the map of twins after repetition + UpdateTwins(aTranslationHistory, aGluingHistory); + + return myRepeatedShape; +} + +//======================================================================= +//function : UpdateTwins +//purpose : Updates the map of twins after repetition +//======================================================================= +void BOPAlgo_MakePeriodic::UpdateTwins(const BRepTools_History& theTranslationHistory, + const BRepTools_History& theGluingHistory) +{ + if (myTwins.IsEmpty()) + return; + + if (myRepeatedTwins.IsEmpty()) + myRepeatedTwins = myTwins; + + // New twins + TopTools_DataMapOfShapeListOfShape aNewTwinsMap; + + // Fence map to avoid repeated fill for the twins + TopTools_MapOfShape aMTwinsDone; + + // Update the map of twins with the new repeated shapes + TopTools_DataMapIteratorOfDataMapOfShapeListOfShape itDMap(myRepeatedTwins); + for (; itDMap.More(); itDMap.Next()) + { + const TopoDS_Shape& aS = itDMap.Key(); + aMTwinsDone.Add(aS); + + const TopTools_ListOfShape& aLTwins = itDMap.Value(); + + // Check if the twins have not been already processed + TopTools_ListIteratorOfListOfShape itLT(aLTwins); + for (; itLT.More(); itLT.Next()) + { + if (aMTwinsDone.Contains(itLT.Value())) + break; + } + if (itLT.More()) + // Group of twins has already been processed + continue; + + // All shapes generated from the shape itself and generated + // from its twins will be the new twins for the shape + TopTools_IndexedMapOfShape aNewGroup; + itLT.Initialize(aLTwins); + + for (Standard_Boolean bShape = Standard_True; itLT.More();) + { + const TopoDS_Shape& aTwin = bShape ? aS : itLT.Value(); + const TopTools_ListOfShape& aLG = theTranslationHistory.Generated(aTwin); + TopTools_ListIteratorOfListOfShape itLG(aLG); + for (; itLG.More(); itLG.Next()) + { + const TopoDS_Shape& aG = itLG.Value(); + const TopTools_ListOfShape& aLM = theGluingHistory.Modified(aG); + if (aLM.IsEmpty()) + aNewGroup.Add(aG); + else + { + TopTools_ListIteratorOfListOfShape itLM(aLM); + for (; itLM.More(); itLM.Next()) + aNewGroup.Add(itLM.Value()); + } + } + + if (bShape) + bShape = Standard_False; + else + itLT.Next(); + } + + // Associate the twins to each other + const Standard_Integer aNbTwins = aNewGroup.Extent(); + for (Standard_Integer i = 1; i <= aNbTwins; ++i) + { + TopTools_ListOfShape* pTwins = aNewTwinsMap.Bound(aNewGroup(i), TopTools_ListOfShape()); + for (Standard_Integer j = 1; j <= aNbTwins; ++j) + if (i != j) pTwins->Append(aNewGroup(j)); + } + } + + myRepeatedTwins = aNewTwinsMap; +} diff --git a/src/BOPAlgo/BOPAlgo_MakePeriodic.hxx b/src/BOPAlgo/BOPAlgo_MakePeriodic.hxx new file mode 100644 index 0000000000..8385dfff0a --- /dev/null +++ b/src/BOPAlgo/BOPAlgo_MakePeriodic.hxx @@ -0,0 +1,603 @@ +// Created on: 2018-03-16 +// Created by: Eugeny MALTCHIKOV +// Copyright (c) 2018 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _BOPAlgo_MakePeriodic_HeaderFile +#define _BOPAlgo_MakePeriodic_HeaderFile + +#include +#include +#include + +#include +#include +#include +#include +#include + +//! BOPAlgo_MakePeriodic is the tool for making an arbitrary shape periodic +//! in 3D space in specified directions. +//! +//! Periodicity of the shape means that the shape can be repeated in any +//! periodic direction any number of times without creation of the new +//! geometry or splits. +//! +//! The idea is to make the shape look identical on the opposite sides of the +//! periodic directions, so when translating the copy of a shape on the period +//! there will be no coinciding parts of different dimensions. +//! +//! If necessary the algorithm will trim the shape to fit it into the +//! requested period by splitting it by the planes limiting the shape's +//! requested period. +//! +//! For making the shape periodic in certain direction the algorithm performs +//! the following steps: +//! * Creates the copy of the shape and moves it on the period into negative +//! side of the requested direction; +//! * Splits the negative side of the shape by the moved copy, ensuring copying +//! of the geometry from positive side to negative; +//! * Creates the copy of the shape (with already split negative side) and moves +//! it on the period into the positive side of the requested direction; +//! * Splits the positive side of the shape by the moved copy, ensuring copying +//! of the geometry from negative side to positive. +//! +//! The algorithm also associates the identical (or twin) shapes located +//! on the opposite sides of the result shape. +//! Using the *GetTwins()* method it is possible to get the twin shapes from +//! the opposite sides. +//! +//! Algorithm also provides the methods to repeat the periodic shape in +//! periodic directions. The subsequent repetitions are performed on the +//! repeated shape, thus repeating the shape two times in X direction will +//! create result in three shapes (original plus two copies). +//! Single subsequent repetition will result already in 6 shapes. +//! The repetitions can be cleared and started over. +//! +//! The algorithm supports History of shapes modifications, thus +//! it is possible to track how the shape has been changed to make it periodic +//! and what new shapes have been created during repetitions. +//! +//! The algorithm supports the parallel processing mode, which allows faster +//! completion of the operations. +//! +//! The algorithm supports the Error/Warning system and returns the following alerts: +//! - *BOPAlgo_AlertNoPeriodicityRequired* - Error alert is given if no periodicity +//! has been requested in any direction; +//! - *BOPAlgo_AlertUnableToTrim* - Error alert is given if the trimming of the shape +//! for fitting it into requested period has failed; +//! - *BOPAlgo_AlertUnableToMakeIdentical* - Error alert is given if splitting of the +//! shape by its moved copies has failed; +//! - *BOPAlgo_AlertUnableToRepeat* - Warning alert is given if the gluing of the repeated +//! shapes has failed. +//! +//! Example of usage of the algorithm: +//! ~~~~ +//! TopoDS_Shape aShape = ...; // The shape to make periodic +//! Standard_Boolean bMakeXPeriodic = ...; // Flag for making or not the shape periodic in X direction +//! Standard_Real aXPeriod = ...; // X period for the shape +//! Standard_Boolean isXTrimmed = ...; // Flag defining whether it is necessary to trimming +//! // the shape to fit to X period +//! Standard_Real aXFirst = ...; // Start of the X period +//! // (really necessary only if the trimming is requested) +//! Standard_Boolean bRunParallel = ...; // Parallel processing mode or single +//! +//! BOPAlgo_MakePeriodic aPeriodicityMaker; // Periodicity maker +//! aPeriodicityMaker.SetShape(aShape); // Set the shape +//! aPeriodicityMaker.MakeXPeriodic(bMakePeriodic, aXPeriod); // Making the shape periodic in X direction +//! aPeriodicityMaker.SetTrimmed(isXTrimmed, aXFirst); // Trim the shape to fit X period +//! aPeriodicityMaker.SetRunParallel(bRunParallel); // Set the parallel processing mode +//! aPeriodicityMaker.Perform(); // Performing the operation +//! +//! if (aPeriodicityMaker.HasErrors()) // Check for the errors +//! { +//! // errors treatment +//! Standard_SStream aSStream; +//! aPeriodicityMaker.DumpErrors(aSStream); +//! return; +//! } +//! if (aPeriodicityMaker.HasWarnings()) // Check for the warnings +//! { +//! // warnings treatment +//! Standard_SStream aSStream; +//! aPeriodicityMaker.DumpWarnings(aSStream); +//! } +//! const TopoDS_Shape& aPeriodicShape = aPeriodicityMaker.Shape(); // Result periodic shape +//! +//! +//! aPeriodicityMaker.XRepeat(2); // Making repetitions +//! const TopoDS_Shape& aRepeat = aPeriodicityMaker.RepeatedShape(); // Getting the repeated shape +//! aPeriodicityMaker.ClearRepetitions(); // Clearing the repetitions +//! ~~~~ +//! +class BOPAlgo_MakePeriodic : public BOPAlgo_Options +{ +public: + + DEFINE_STANDARD_ALLOC + +public: //! @name Constructor + + //! Empty constructor + BOPAlgo_MakePeriodic() : BOPAlgo_Options() + { + myRepeatPeriod[0] = myRepeatPeriod[1] = myRepeatPeriod[2] = 0.0; + } + + +public: //! @name Setting the shape to make it periodic + + //! Sets the shape to make it periodic. + //! @param theShape [in] The shape to make periodic. + void SetShape(const TopoDS_Shape& theShape) + { + myInputShape = theShape; + } + + +public: //! @name Definition of the structure to keep all periodicity parameters + + //! Structure to keep all periodicity parameters: + struct PeriodicityParams + { + PeriodicityParams() + { + Clear(); + } + + //! Returns all previously set parameters to default values + void Clear() + { + myPeriodic[0] = myPeriodic[1] = myPeriodic[2] = Standard_False; + myPeriod[0] = myPeriod[1] = myPeriod[2] = 0.0; + myIsTrimmed[0] = myIsTrimmed[1] = myIsTrimmed[2] = Standard_True; + myPeriodFirst[0] = myPeriodFirst[1] = myPeriodFirst[2] = 0.0; + } + + Standard_Boolean myPeriodic[3]; //!< Array of flags defining whether the shape should be + //! periodic in XYZ directions + Standard_Real myPeriod[3]; //!< Array of XYZ period values. Defining the period for any + //! direction the corresponding flag for that direction in + //! myPeriodic should be set to true + Standard_Boolean myIsTrimmed[3]; //!< Array of flags defining whether the input shape has to be + //! trimmed to fit the required period in the required direction + Standard_Real myPeriodFirst[3]; //!< Array of start parameters of the XYZ periods: required for trimming + }; + + +public: //! @name Setters/Getters for periodicity parameters structure + + //! Sets the periodicity parameters. + //! @param theParams [in] Periodicity parameters + void SetPeriodicityParameters(const PeriodicityParams& theParams) + { + myPeriodicityParams = theParams; + } + + const PeriodicityParams& PeriodicityParameters() const + { + return myPeriodicityParams; + } + + +public: //! @name Methods for setting/getting periodicity info using ID as a direction + + //! Sets the flag to make the shape periodic in specified direction: + //! - 0 - X direction; + //! - 1 - Y direction; + //! - 2 - Z direction. + //! + //! @param theDirectionID [in] The direction's ID; + //! @param theIsPeriodic [in] Flag defining periodicity in given direction; + //! @param thePeriod [in] Required period in given direction. + void MakePeriodic(const Standard_Integer theDirectionID, + const Standard_Boolean theIsPeriodic, + const Standard_Real thePeriod = 0.0) + { + Standard_Integer id = ToDirectionID(theDirectionID); + myPeriodicityParams.myPeriodic[id] = theIsPeriodic; + myPeriodicityParams.myPeriod[id] = theIsPeriodic ? thePeriod : 0.0; + } + + //! Returns the info about Periodicity of the shape in specified direction. + //! @param theDirectionID [in] The direction's ID. + Standard_Boolean IsPeriodic(const Standard_Integer theDirectionID) const + { + return myPeriodicityParams.myPeriodic[ToDirectionID(theDirectionID)]; + } + + //! Returns the Period of the shape in specified direction. + //! @param theDirectionID [in] The direction's ID. + Standard_Real Period(const Standard_Integer theDirectionID) const + { + Standard_Integer id = ToDirectionID(theDirectionID); + return myPeriodicityParams.myPeriodic[id] ? myPeriodicityParams.myPeriod[id] : 0.0; + } + + +public: //! @name Named methods for setting/getting info about shape's periodicity + + //! Sets the flag to make the shape periodic in X direction. + //! @param theIsPeriodic [in] Flag defining periodicity in X direction; + //! @param thePeriod [in] Required period in X direction. + void MakeXPeriodic(const Standard_Boolean theIsPeriodic, + const Standard_Real thePeriod = 0.0) + { + MakePeriodic(0, theIsPeriodic, thePeriod); + } + + //! Returns the info about periodicity of the shape in X direction. + Standard_Boolean IsXPeriodic() const { return IsPeriodic(0); } + + //! Returns the XPeriod of the shape + Standard_Real XPeriod() const { return Period(0); } + + //! Sets the flag to make the shape periodic in Y direction. + //! @param theIsPeriodic [in] Flag defining periodicity in Y direction; + //! @param thePeriod [in] Required period in Y direction. + void MakeYPeriodic(const Standard_Boolean theIsPeriodic, + const Standard_Real thePeriod = 0.0) + { + MakePeriodic(1, theIsPeriodic, thePeriod); + } + + //! Returns the info about periodicity of the shape in Y direction. + Standard_Boolean IsYPeriodic() const { return IsPeriodic(1); } + + //! Returns the YPeriod of the shape. + Standard_Real YPeriod() const { return Period(1); } + + //! Sets the flag to make the shape periodic in Z direction. + //! @param theIsPeriodic [in] Flag defining periodicity in Z direction; + //! @param thePeriod [in] Required period in Z direction. + void MakeZPeriodic(const Standard_Boolean theIsPeriodic, + const Standard_Real thePeriod = 0.0) + { + MakePeriodic(2, theIsPeriodic, thePeriod); + } + + //! Returns the info about periodicity of the shape in Z direction. + Standard_Boolean IsZPeriodic() const { return IsPeriodic(2); } + + //! Returns the ZPeriod of the shape. + Standard_Real ZPeriod() const { return Period(2); } + + +public: //! @name Methods for setting/getting trimming info taking Direction ID as a parameter + + //! Defines whether the input shape is already trimmed in specified direction + //! to fit the period in this direction. + //! Direction is defined by an ID: + //! - 0 - X direction; + //! - 1 - Y direction; + //! - 2 - Z direction. + //! + //! If the shape is not trimmed it is required to set the first parameter + //! of the period in that direction. + //! The algorithm will make the shape fit into the period. + //! + //! Before calling this method, the shape has to be set to be periodic in this direction. + //! + //! @param theDirectionID [in] The direction's ID; + //! @param theIsTrimmed [in] The flag defining trimming of the shape in given direction; + //! @param theFirst [in] The first periodic parameter in the given direction. + void SetTrimmed(const Standard_Integer theDirectionID, + const Standard_Boolean theIsTrimmed, + const Standard_Real theFirst = 0.0) + { + Standard_Integer id = ToDirectionID(theDirectionID); + if (IsPeriodic(id)) + { + myPeriodicityParams.myIsTrimmed[id] = theIsTrimmed; + myPeriodicityParams.myPeriodFirst[id] = !theIsTrimmed ? theFirst : 0.0; + } + } + + //! Returns whether the input shape was trimmed in the specified direction. + //! @param theDirectionID [in] The direction's ID. + Standard_Boolean IsInputTrimmed(const Standard_Integer theDirectionID) const + { + return myPeriodicityParams.myIsTrimmed[ToDirectionID(theDirectionID)]; + } + + //! Returns the first periodic parameter in the specified direction. + //! @param theDirectionID [in] The direction's ID. + Standard_Real PeriodFirst(const Standard_Integer theDirectionID) const + { + Standard_Integer id = ToDirectionID(theDirectionID); + return !myPeriodicityParams.myIsTrimmed[id] ? myPeriodicityParams.myPeriodFirst[id] : 0.0; + } + + +public: //! @name Named methods for setting/getting trimming info + + //! Defines whether the input shape is already trimmed in X direction + //! to fit the X period. If the shape is not trimmed it is required + //! to set the first parameter for the X period. + //! The algorithm will make the shape fit into the period. + //! + //! Before calling this method, the shape has to be set to be periodic in this direction. + //! + //! @param theIsTrimmed [in] Flag defining whether the shape is already trimmed + //! in X direction to fit the X period; + //! @param theFirst [in] The first X periodic parameter. + void SetXTrimmed(const Standard_Boolean theIsTrimmed, + const Standard_Boolean theFirst = 0.0) + { + SetTrimmed(0, theIsTrimmed, theFirst); + } + + //! Returns whether the input shape was already trimmed for X period. + Standard_Boolean IsInputXTrimmed() const + { + return IsInputTrimmed(0); + } + + //! Returns the first parameter for the X period. + Standard_Real XPeriodFirst() const + { + return PeriodFirst(0); + } + + //! Defines whether the input shape is already trimmed in Y direction + //! to fit the Y period. If the shape is not trimmed it is required + //! to set the first parameter for the Y period. + //! The algorithm will make the shape fit into the period. + //! + //! Before calling this method, the shape has to be set to be periodic in this direction. + //! + //! @param theIsTrimmed [in] Flag defining whether the shape is already trimmed + //! in Y direction to fit the Y period; + //! @param theFirst [in] The first Y periodic parameter. + void SetYTrimmed(const Standard_Boolean theIsTrimmed, + const Standard_Boolean theFirst = 0.0) + { + SetTrimmed(1, theIsTrimmed, theFirst); + } + + //! Returns whether the input shape was already trimmed for Y period. + Standard_Boolean IsInputYTrimmed() const + { + return IsInputTrimmed(1); + } + + //! Returns the first parameter for the Y period. + Standard_Real YPeriodFirst() const + { + return PeriodFirst(1); + } + + //! Defines whether the input shape is already trimmed in Z direction + //! to fit the Z period. If the shape is not trimmed it is required + //! to set the first parameter for the Z period. + //! The algorithm will make the shape fit into the period. + //! + //! Before calling this method, the shape has to be set to be periodic in this direction. + //! + //! @param theIsTrimmed [in] Flag defining whether the shape is already trimmed + //! in Z direction to fit the Z period; + //! @param theFirst [in] The first Z periodic parameter. + void SetZTrimmed(const Standard_Boolean theIsTrimmed, + const Standard_Boolean theFirst = 0.0) + { + SetTrimmed(2, theIsTrimmed, theFirst); + } + + //! Returns whether the input shape was already trimmed for Z period. + Standard_Boolean IsInputZTrimmed() const + { + return IsInputTrimmed(2); + } + + //! Returns the first parameter for the Z period. + Standard_Real ZPeriodFirst() const + { + return PeriodFirst(2); + } + +public: //! @name Performing the operation + + //! Makes the shape periodic in necessary directions + Standard_EXPORT void Perform(); + + +public: //! @name Using the algorithm to repeat the shape + + //! Performs repetition of the shape in specified direction + //! required number of times. + //! Negative value of times means that the repetition should + //! be perform in negative direction. + //! Makes the repeated shape a base for following repetitions. + //! + //! @param theDirectionID [in] The direction's ID; + //! @param theTimes [in] Requested number of repetitions. + Standard_EXPORT const TopoDS_Shape& RepeatShape(const Standard_Integer theDirectionID, + const Standard_Integer theTimes); + + //! Repeats the shape in X direction specified number of times. + //! Negative value of times means that the repetition should be + //! perform in negative X direction. + //! Makes the repeated shape a base for following repetitions. + //! + //! @param theTimes [in] Requested number of repetitions. + const TopoDS_Shape& XRepeat(const Standard_Integer theTimes) + { + return RepeatShape(0, theTimes); + } + + //! Repeats the shape in Y direction specified number of times. + //! Negative value of times means that the repetition should be + //! perform in negative Y direction. + //! Makes the repeated shape a base for following repetitions. + //! + //! @param theTimes [in] Requested number of repetitions. + const TopoDS_Shape& YRepeat(const Standard_Integer theTimes) + { + return RepeatShape(1, theTimes); + } + + //! Repeats the shape in Z direction specified number of times. + //! Negative value of times means that the repetition should be + //! perform in negative Z direction. + //! Makes the repeated shape a base for following repetitions. + //! + //! @param theTimes [in] Requested number of repetitions. + const TopoDS_Shape& ZRepeat(const Standard_Integer theTimes) + { + return RepeatShape(2, theTimes); + } + + +public: //! @name Starting the repetitions over + + //! Returns the repeated shape + const TopoDS_Shape& RepeatedShape() const { return myRepeatedShape; } + + //! Clears all performed repetitions. + //! The next repetition will be performed on the base shape. + void ClearRepetitions() + { + myRepeatPeriod[0] = myRepeatPeriod[1] = myRepeatPeriod[2] = 0.0; + myRepeatedShape.Nullify(); + myRepeatedTwins.Clear(); + if (!myHistory.IsNull()) + { + myHistory->Clear(); + if (!mySplitHistory.IsNull()) + myHistory->Merge(mySplitHistory); + } + } + +public: //! @name Obtaining the result shape + + //! Returns the resulting periodic shape + const TopoDS_Shape& Shape() const { return myShape; } + + +public: //! @name Getting the identical shapes + + //! Returns the identical shapes for the given shape located + //! on the opposite periodic side. + //! Returns empty list in case the shape has no twin. + //! + //! @param theS [in] Shape to get the twins for. + const TopTools_ListOfShape& GetTwins(const TopoDS_Shape& theS) const + { + static TopTools_ListOfShape empty; + const TopTools_ListOfShape* aTwins = + myRepeatedTwins.IsEmpty() ? myTwins.Seek(theS) : myRepeatedTwins.Seek(theS); + return (aTwins ? *aTwins : empty); + } + + +public: //! @name Getting the History of the algorithm + + //! Returns the History of the algorithm + const Handle(BRepTools_History)& History() const + { + return myHistory; + } + +public: //! @name Clearing the algorithm from previous runs + + //! Clears the algorithm from previous runs + void Clear() + { + BOPAlgo_Options::Clear(); + myPeriodicityParams.Clear(); + myShape.Nullify(); + if (!mySplitHistory.IsNull()) + mySplitHistory->Clear(); + if (!myHistory.IsNull()) + myHistory->Clear(); + + ClearRepetitions(); + } + + +public: //! @name Conversion of the integer to ID of periodic direction + + //! Converts the integer to ID of periodic direction + static Standard_Integer ToDirectionID(const Standard_Integer theDirectionID) + { + return Abs(theDirectionID % 3); + } + + +protected: //! @name Protected methods performing the operation + + //! Checks the validity of input data + Standard_EXPORT void CheckData(); + + //! Trims the shape to fit to the periodic bounds + Standard_EXPORT void Trim(); + + //! Makes the shape identical on opposite sides + Standard_EXPORT void MakeIdentical(); + + //! Splits the negative side of the shape with the geometry + //! located on the positive side copying the geometry from + //! positive side to the negative. + Standard_EXPORT void SplitNegative(); + + //! Splits the positive side of the shape with the geometry + //! located on the negative side of the shape. + //! Ensures that the geometries on the opposite sides will + //! be identical. + //! Associates the identical opposite sub-shapes. + Standard_EXPORT void SplitPositive(); + + //! Splits the shape by the given tools, copying the geometry of coinciding + //! parts from the given tools to the split shape. + //! @param theTools [in] The tools to split the shape and take the geometry + //! for coinciding parts. + //! @param theSplitShapeHistory [out] The history of shape split + //! @param theSplitToolsHistory [out] The history of tools modifications during the split + Standard_EXPORT void SplitShape(const TopTools_ListOfShape& theTools, + Handle(BRepTools_History) theSplitShapeHistory = NULL, + Handle(BRepTools_History) theSplitToolsHistory = NULL); + + //! Updates the map of twins after periodic shape repetition. + //! @param theTranslationHistory [in] The history of translation of the periodic shape. + //! @param theGluingHistory [in] The history of gluing of the repeated shapes. + Standard_EXPORT void UpdateTwins(const BRepTools_History& theTranslationHistory, + const BRepTools_History& theGluingHistory); + + +protected: //! @name Fields + + // Inputs + TopoDS_Shape myInputShape; //!< Input shape to make periodic + + PeriodicityParams myPeriodicityParams; //!< Periodicity parameters + + // Results + TopoDS_Shape myShape; //!< Resulting periodic shape (base for repetitions) + TopoDS_Shape myRepeatedShape; //!< Resulting shape after making repetitions of the base + Standard_Real myRepeatPeriod[3]; //!< XYZ repeat period + TopTools_DataMapOfShapeListOfShape myRepeatedTwins; //!< Map of associations of the identical sub-shapes + //! after repetition of the periodic shape + + // Twins + TopTools_DataMapOfShapeListOfShape myTwins; //!< Map of associations of the identical sub-shapes + //! located on the opposite sides of the shape + + // History + Handle(BRepTools_History) mySplitHistory; //!< Split history - history of shapes modification + //! after the split for making the shape periodic + Handle(BRepTools_History) myHistory; //!< Final history of shapes modifications + //! (to include the history of shape repetition) + +}; + +#endif // _BOPAlgo_MakePeriodic_HeaderFile diff --git a/src/BOPAlgo/BOPAlgo_PaveFiller.cxx b/src/BOPAlgo/BOPAlgo_PaveFiller.cxx index 421c9fa8d3..c48fca5716 100644 --- a/src/BOPAlgo/BOPAlgo_PaveFiller.cxx +++ b/src/BOPAlgo/BOPAlgo_PaveFiller.cxx @@ -173,22 +173,6 @@ void BOPAlgo_PaveFiller::SetSectionAttribute mySectionAttribute = theSecAttr; } //======================================================================= -//function : SetArguments -//purpose : -//======================================================================= -void BOPAlgo_PaveFiller::SetArguments(const TopTools_ListOfShape& theLS) -{ - myArguments=theLS; -} -//======================================================================= -//function : Arguments -//purpose : -//======================================================================= -const TopTools_ListOfShape& BOPAlgo_PaveFiller::Arguments()const -{ - return myArguments; -} -//======================================================================= // function: Init // purpose: //======================================================================= diff --git a/src/BOPAlgo/BOPAlgo_PaveFiller.hxx b/src/BOPAlgo/BOPAlgo_PaveFiller.hxx index 8baf0fac55..f93d867003 100644 --- a/src/BOPAlgo/BOPAlgo_PaveFiller.hxx +++ b/src/BOPAlgo/BOPAlgo_PaveFiller.hxx @@ -125,9 +125,23 @@ public: Standard_EXPORT const BOPDS_PIterator& Iterator(); - Standard_EXPORT void SetArguments (const TopTools_ListOfShape& theLS); - - Standard_EXPORT const TopTools_ListOfShape& Arguments() const; + //! Sets the arguments for operation + void SetArguments (const TopTools_ListOfShape& theLS) + { + myArguments = theLS; + } + + //! Adds the argument for operation + void AddArgument(const TopoDS_Shape& theShape) + { + myArguments.Append(theShape); + } + + //! Returns the list of arguments + const TopTools_ListOfShape& Arguments() const + { + return myArguments; + } Standard_EXPORT const Handle(IntTools_Context)& Context(); diff --git a/src/BOPAlgo/BOPAlgo_Splitter.cxx b/src/BOPAlgo/BOPAlgo_Splitter.cxx index 6f93a9c927..c0bea0d80a 100644 --- a/src/BOPAlgo/BOPAlgo_Splitter.cxx +++ b/src/BOPAlgo/BOPAlgo_Splitter.cxx @@ -17,6 +17,8 @@ #include #include +#include + //======================================================================= //function : //purpose : @@ -98,3 +100,31 @@ void BOPAlgo_Splitter::Perform() myEntryPoint = 1; PerformInternal(*pPF); } + +//======================================================================= +//function : BuildResult +//purpose : +//======================================================================= +void BOPAlgo_Splitter::BuildResult(const TopAbs_ShapeEnum theType) +{ + BOPAlgo_Builder::BuildResult(theType); + + if (theType == TopAbs_COMPOUND) + { + // The method is called for the last time for this operation. + // If there is only one argument shape and it has been modified into + // a single shape, or has not been modified at all, the result shape + // has to be overwritten to avoid the unnecessary enclosure into compound. + if (myArguments.Extent() == 1) + { + TopoDS_Iterator it(myShape); + if (it.More()) + { + const TopoDS_Shape& aSFirst = it.Value(); + it.Next(); + if (!it.More()) + myShape = aSFirst; + } + } + } +} diff --git a/src/BOPAlgo/BOPAlgo_Splitter.hxx b/src/BOPAlgo/BOPAlgo_Splitter.hxx index 62a878989e..67189f3160 100644 --- a/src/BOPAlgo/BOPAlgo_Splitter.hxx +++ b/src/BOPAlgo/BOPAlgo_Splitter.hxx @@ -66,6 +66,12 @@ protected: //! Checks the input data Standard_EXPORT virtual void CheckData() Standard_OVERRIDE; + + //! Adds images of the argument shapes into result. + //! When called the for the last time (for compound) it rebuilds the result + //! shape to avoid multiple enclosure into compounds. + Standard_EXPORT virtual void BuildResult(const TopAbs_ShapeEnum theType) Standard_OVERRIDE; + }; #endif // _BOPAlgo_Splitter_HeaderFile diff --git a/src/BOPAlgo/FILES b/src/BOPAlgo/FILES index e2a3151a9e..50b6cec32d 100644 --- a/src/BOPAlgo/FILES +++ b/src/BOPAlgo/FILES @@ -27,6 +27,8 @@ BOPAlgo_CheckResult.cxx BOPAlgo_CheckResult.hxx BOPAlgo_CheckStatus.hxx BOPAlgo_ListOfCheckResult.hxx +BOPAlgo_MakePeriodic.cxx +BOPAlgo_MakePeriodic.hxx BOPAlgo_MakerVolume.cxx BOPAlgo_MakerVolume.hxx BOPAlgo_MakerVolume.lxx diff --git a/src/BOPTest/BOPTest.cxx b/src/BOPTest/BOPTest.cxx index 790495e41b..873c1843fd 100644 --- a/src/BOPTest/BOPTest.cxx +++ b/src/BOPTest/BOPTest.cxx @@ -57,6 +57,7 @@ void BOPTest::AllCommands(Draw_Interpretor& theCommands) BOPTest::CellsCommands (theCommands); BOPTest::UtilityCommands (theCommands); BOPTest::RemoveFeaturesCommands(theCommands); + BOPTest::PeriodicityCommands(theCommands); } //======================================================================= //function : Factory diff --git a/src/BOPTest/BOPTest.hxx b/src/BOPTest/BOPTest.hxx index 20fbd91803..abf338dbe8 100644 --- a/src/BOPTest/BOPTest.hxx +++ b/src/BOPTest/BOPTest.hxx @@ -60,6 +60,8 @@ public: Standard_EXPORT static void RemoveFeaturesCommands (Draw_Interpretor& aDI); + Standard_EXPORT static void PeriodicityCommands (Draw_Interpretor& aDI); + //! Prints errors and warnings if any and draws attached shapes //! if flag BOPTest_Objects::DrawWarnShapes() is set Standard_EXPORT static void ReportAlerts (const Handle(Message_Report)& theReport); diff --git a/src/BOPTest/BOPTest_PeriodicityCommands.cxx b/src/BOPTest/BOPTest_PeriodicityCommands.cxx new file mode 100644 index 0000000000..571745e832 --- /dev/null +++ b/src/BOPTest/BOPTest_PeriodicityCommands.cxx @@ -0,0 +1,293 @@ +// Created on: 03/19/2018 +// Created by: Eugeny MALTCHIKOV +// Copyright (c) 2018 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include + +#include + +#include +#include + +#include + +#include + +#include +#include + +#include +#include + +static Standard_Integer MakePeriodic(Draw_Interpretor&, Standard_Integer, const char**); +static Standard_Integer GetTwins(Draw_Interpretor&, Standard_Integer, const char**); +static Standard_Integer RepeatShape(Draw_Interpretor&, Standard_Integer, const char**); +static Standard_Integer ClearRepetitions(Draw_Interpretor&, Standard_Integer, const char**); + +namespace +{ + static BOPAlgo_MakePeriodic ThePeriodicityMaker; +} + +//======================================================================= +//function : PeriodicityCommands +//purpose : +//======================================================================= +void BOPTest::PeriodicityCommands(Draw_Interpretor& theCommands) +{ + static Standard_Boolean done = Standard_False; + if (done) return; + done = Standard_True; + // Chapter's name + const char* group = "BOPTest commands"; + // Commands + theCommands.Add("makeperiodic", "makeperiodic result shape [-x/y/z period [-trim first]]\n" + "\t\tMake the shape periodic in the required directions.\n" + "\t\tresult - resulting periodic shape;\n" + "\t\t-x/y/z period - option to make the shape periodic in X, Y or Z\n " + "\t\t direction with the given period;\n" + "\t\t-trim first - option to trim the shape to fit the required period,\n" + "\t\t starting the period in first.", + __FILE__, MakePeriodic, group); + + theCommands.Add("periodictwins", "periodictwins twins shape\n" + "\t\tReturns the twins for the shape located on the opposite side of the periodic shape.", + __FILE__, GetTwins, group); + + // Repetition commands + theCommands.Add("repeatshape", "repeatshape result -x/y/z times\n" + "\t\tRepeats the periodic shape in periodic directions required number of times.\n" + "\t\tresult - resulting shape;\n" + "\t\t-x/y/z times - direction for repetition and number of repetitions.", + __FILE__, RepeatShape, group); + + theCommands.Add("clearrepetitions", "clearrepetitions\n" + "\t\tClears all previous repetitions of the periodic shape (used without any arguments).", + __FILE__, ClearRepetitions, group); +} + +//======================================================================= +//function : MakePeriodic +//purpose : +//======================================================================= +Standard_Integer MakePeriodic(Draw_Interpretor& theDI, + Standard_Integer theArgc, + const char ** theArgv) +{ + if (theArgc < 5) + { + theDI.PrintHelp(theArgv[0]); + return 1; + } + + // Get the shape to make periodic + TopoDS_Shape aShape = DBRep::Get(theArgv[2]); + if (aShape.IsNull()) + { + theDI << "Error: " << theArgv[2] << " is a null shape.\n"; + return 1; + } + + ThePeriodicityMaker.Clear(); + ThePeriodicityMaker.SetShape(aShape); + + for (Standard_Integer i = 3; i < theArgc;) + { + // Get periodicity + Standard_Integer iDir = i; + + Standard_Integer aDirID = -1; + if (!strcasecmp(theArgv[i], "-x")) + aDirID = 0; + else if (!strcasecmp(theArgv[i], "-y")) + aDirID = 1; + else if (!strcasecmp(theArgv[i], "-z")) + aDirID = 2; + else + { + theDI << theArgv[i] << " - Invalid key\n"; + return 1; + } + + char cDirName[2]; + sprintf(cDirName, "%c", theArgv[iDir][1]); + + if (theArgc == (i + 1)) + { + theDI << "Period for " << cDirName << " direction is not set\n"; + return 1; + } + + Standard_Real aPeriod = Draw::Atof(theArgv[++i]); + + ThePeriodicityMaker.MakePeriodic(aDirID, Standard_True, aPeriod); + + ++i; + if (theArgc > i + 1) + { + // Check if trimming is necessary + if (!strcmp(theArgv[i], "-trim")) + { + if (theArgc == (i + 1)) + { + theDI << "Trim bounds for " << cDirName << " direction are not set\n"; + return 1; + } + Standard_Real aFirst = Draw::Atof(theArgv[++i]); + + ThePeriodicityMaker.SetTrimmed(aDirID, Standard_False, aFirst); + ++i; + } + } + } + + ThePeriodicityMaker.SetRunParallel(BOPTest_Objects::RunParallel()); + + // Perform operation + ThePeriodicityMaker.Perform(); + + // Print Error/Warning messages + BOPTest::ReportAlerts(ThePeriodicityMaker.GetReport()); + + // Set the history of the operation in session + BRepTest_Objects::SetHistory(ThePeriodicityMaker.History()); + + if (ThePeriodicityMaker.HasErrors()) + return 0; + + // Draw the result shape + const TopoDS_Shape& aResult = ThePeriodicityMaker.Shape(); + DBRep::Set(theArgv[1], aResult); + + return 0; +} + +//======================================================================= +//function : GetTwin +//purpose : +//======================================================================= +Standard_Integer GetTwins(Draw_Interpretor& theDI, + Standard_Integer theArgc, + const char ** theArgv) +{ + if (theArgc != 3) + { + theDI.PrintHelp(theArgv[0]); + return 1; + } + + // Get the shape to find twins + TopoDS_Shape aShape = DBRep::Get(theArgv[2]); + if (aShape.IsNull()) + { + theDI << "Error: " << theArgv[2] << " is a null shape.\n"; + return 1; + } + + const TopTools_ListOfShape& aTwins = ThePeriodicityMaker.GetTwins(aShape); + + TopoDS_Shape aCTwins; + if (aTwins.IsEmpty()) + theDI << "No twins for the shape.\n"; + else if (aTwins.Extent() == 1) + aCTwins = aTwins.First(); + else + { + BRep_Builder().MakeCompound(TopoDS::Compound(aCTwins)); + for (TopTools_ListIteratorOfListOfShape it(aTwins); it.More(); it.Next()) + BRep_Builder().Add(aCTwins, it.Value()); + } + + DBRep::Set(theArgv[1], aCTwins); + + return 0; +} + +//======================================================================= +//function : RepeatShape +//purpose : +//======================================================================= +Standard_Integer RepeatShape(Draw_Interpretor& theDI, + Standard_Integer theArgc, + const char ** theArgv) +{ + if (theArgc < 4) + { + theDI.PrintHelp(theArgv[0]); + return 1; + } + + for (Standard_Integer i = 2; i < theArgc; ++i) + { + Standard_Integer aDirID = -1; + if (!strcasecmp(theArgv[i], "-x")) + aDirID = 0; + else if (!strcasecmp(theArgv[i], "-y")) + aDirID = 1; + else if (!strcasecmp(theArgv[i], "-z")) + aDirID = 2; + else + { + theDI << theArgv[i] << " - Invalid key\n"; + return 1; + } + + char cDirName[2]; + sprintf(cDirName, "%c", theArgv[i][1]); + + Standard_Integer aTimes = 0; + if (theArgc > i + 1) + aTimes = Draw::Atoi(theArgv[++i]); + + if (aTimes == 0) + { + theDI << "Number of repetitions for " << cDirName << " direction is not set\n"; + return 1; + } + + ThePeriodicityMaker.RepeatShape(aDirID, aTimes); + } + + // Print Error/Warning messages + BOPTest::ReportAlerts(ThePeriodicityMaker.GetReport()); + + // Set the history of the operation in session + BRepTest_Objects::SetHistory(ThePeriodicityMaker.History()); + + if (ThePeriodicityMaker.HasErrors()) + return 0; + + // Draw the result shape + const TopoDS_Shape& aResult = ThePeriodicityMaker.RepeatedShape(); + DBRep::Set(theArgv[1], aResult); + + return 0; +} + +//======================================================================= +//function : ClearRepetitions +//purpose : +//======================================================================= +Standard_Integer ClearRepetitions(Draw_Interpretor&, + Standard_Integer, + const char **) +{ + // Clear all previous repetitions + ThePeriodicityMaker.ClearRepetitions(); + + // Set the history of the operation in session + BRepTest_Objects::SetHistory(ThePeriodicityMaker.History()); + + return 0; +} diff --git a/src/BOPTest/FILES b/src/BOPTest/FILES index 2c97830e7e..a2ceac1a98 100755 --- a/src/BOPTest/FILES +++ b/src/BOPTest/FILES @@ -11,6 +11,7 @@ BOPTest_Objects.cxx BOPTest_Objects.hxx BOPTest_OptionCommands.cxx BOPTest_PartitionCommands.cxx +BOPTest_PeriodicityCommands.cxx BOPTest_TolerCommands.cxx BOPTest_DebugCommands.cxx BOPTest_CellsCommands.cxx diff --git a/tests/boolean/grids.list b/tests/boolean/grids.list index 4a295c5475..b90dc67e26 100644 --- a/tests/boolean/grids.list +++ b/tests/boolean/grids.list @@ -30,4 +30,5 @@ 030 history 031 removefeatures 032 simplify -033 opensolid \ No newline at end of file +033 opensolid +034 periodicity \ No newline at end of file diff --git a/tests/boolean/periodicity/A1 b/tests/boolean/periodicity/A1 new file mode 100644 index 0000000000..d23cc93aba --- /dev/null +++ b/tests/boolean/periodicity/A1 @@ -0,0 +1,15 @@ +box b 10 10 10 + +# make the box periodic +makeperiodic bp b -x 5 -trim 2 -y 8 -trim 1 -z 12 -trim -1 + +checkshape bp +checknbshapes bp -vertex 8 -edge 12 -wire 6 -face 6 -shell 1 -solid 1 -t +checkprops bp -s 340 -v 400 + +# repeat the shape +repeatshape result -x 5 -y 5 -z 5 + +checkshape result +checknbshapes result -vertex 588 -edge 1302 -wire 936 -face 936 -shell 216 -solid 216 -t +checkprops result -s 73440 -v 86400 diff --git a/tests/boolean/periodicity/A2 b/tests/boolean/periodicity/A2 new file mode 100644 index 0000000000..bfca7bfefb --- /dev/null +++ b/tests/boolean/periodicity/A2 @@ -0,0 +1,80 @@ +polyline p 0 0 0 10 0 0 10 0 10 5 0 10 5 0 5 0 0 5 0 0 0 +mkplane f p +prism s f 0 5 0 + +# make the shape periodic +makeperiodic sp s -x 10 -z 10 -y 5 + +checkshape sp +checknbshapes sp -vertex 16 -edge 24 -wire 10 -face 10 -shell 1 -solid 1 -t +checkprops sp -s 350 -v 375 + +# get history of the operation +savehistory h + +# check modification of the bottom and side faces +explode s f +modified m h s_1 + +checknbshapes m -vertex 6 -edge 7 -wire 2 -face 2 -t +explode m f +periodictwins t1 m_1 +periodictwins t2 m_2 + +compound t1 t2 t +checknbshapes t -face 1 -t + + +explode s f +modified m h s_2 + +checknbshapes m -vertex 6 -edge 7 -wire 2 -face 2 -t +explode m f +periodictwins t1 m_1 +periodictwins t2 m_2 + +compound t1 t2 t +checknbshapes t -face 1 -t + + +# repeat the shape +repeatshape res -x 1 -y 2 + +checkshape res +checknbshapes res -vertex 56 -edge 102 -wire 53 -face 53 -shell 6 -solid 6 -t +checkprops res -s 2100 -v 2250 + + +# get repetition history +savehistory h + +# get generations of bottom face +generated g h s_1 + +checkshape g +checknbshapes g -vertex 20 -edge 31 -wire 12 -face 12 -t +checkprops g -s 300 + +# get generations of the top face +generated g h s_3 + +checkshape g +checknbshapes g -vertex 16 -edge 20 -wire 6 -face 6 -t +checkprops g -s 150 + +foreach f [explode g f] { + periodictwins t $f + checkshape t + checknbshapes t -wire 11 -face 11 -t + checkprops t -s 275 +} + +# clear repetitions +clearrepetitions + +savehistory h + +# now generated for bottom face should be empty +if {![regexp "No shapes were generated from the shape." [generated g1 h s_1]]} { + puts "Error: history is not cleared" +} diff --git a/tests/boolean/periodicity/A3 b/tests/boolean/periodicity/A3 new file mode 100644 index 0000000000..142b43d88d --- /dev/null +++ b/tests/boolean/periodicity/A3 @@ -0,0 +1,14 @@ +box b 100 100 1 + +pcylinder c 2 2 +ttranslate c 2.5 2.5 0 + +makeperiodic p c -x 5 -trim 0 -y 5 -trim 0 + +repeatshape r -x 19 -y 19 + +bcut result b r + +checkshape result +checknbshapes result -vertex 808 -edge 1212 -wire 1206 -face 406 -shell 1 -solid 1 -t +checkprops result -s 15373.5 -v 4973.45 diff --git a/tests/boolean/periodicity/A4 b/tests/boolean/periodicity/A4 new file mode 100644 index 0000000000..3826d39178 --- /dev/null +++ b/tests/boolean/periodicity/A4 @@ -0,0 +1,13 @@ +box b 5 5 1 +pcylinder c 2 2 +ttranslate c 2.5 2.5 0 + +bcut p b c + +makeperiodic p p -x 5 -y 5 + +repeatshape result -x -9 -x 1 -y -9 -y 1 + +checkshape result +checknbshapes result -vertex 1682 -edge 3321 -wire 2840 -face 2040 -shell 400 -solid 400 -t +checkprops result -s 22973.5 -v 4973.45 diff --git a/tests/boolean/periodicity/A5 b/tests/boolean/periodicity/A5 new file mode 100644 index 0000000000..581aa61ccf --- /dev/null +++ b/tests/boolean/periodicity/A5 @@ -0,0 +1,39 @@ +box b 10 10 10 +copy b bf +foreach e [explode b e] { + nurbsconvert ne $e + eval mkvolume s ne [explode bf f] + + savehistory mv_hist + modified m mv_hist ne + + mkcurve cur m + if {![regexp "BSplineCurve" [dump cur]]} { + puts "Error: Boolean Operation took the second face" + } + + # make the shape periodic in all directions + makeperiodic res s -x 10 -y 10 -z 10 + + savehistory h + modified me h m + + # get twins for the edge + periodictwins twins me + + checknbshapes twins -edge 2 -m "Periodic twins" -t + mkcurve c_me me + if {![regexp {Basis curve :\n([^ \n]*)} [dump c_me] full e_type]} { + puts "Error: Unable to get the type" + } + + foreach t [explode twins e] { + mkcurve c_$t $t + if {![regexp {Basis curve :\n([^ \n]*)} [dump c_$t] full t_type]} { + puts "Error: Unable to get the type" + } + if {$e_type != $t_type} { + puts "Error: Twins have different geometries" + } + } +} diff --git a/tests/boolean/periodicity/A6 b/tests/boolean/periodicity/A6 new file mode 100644 index 0000000000..29f035b5f5 --- /dev/null +++ b/tests/boolean/periodicity/A6 @@ -0,0 +1,62 @@ +box b 10 10 10 + +foreach bf [explode b f] { + # replace face with the nurbs one + nurbsconvert nf $bf + eval mkvolume s nf [explode b f] + + savehistory mv_hist + modified m mv_hist nf + + mksurface surf m + if {![regexp "BSplineSurface" [dump surf]]} { + puts "Error: Boolean Operation took the second face" + } + + # make the shape periodic in all directions + makeperiodic res s -x 10 -y 10 -z 10 + + savehistory h + modified mf h m + + mksurface s_mf mf + + # get twins for the face + periodictwins tf mf + + checknbshapes tf -face 1 -m "Periodic twins" -t + + mksurface s_tf tf + + if {![regexp {\*\n([^ \n]*)} [dump s_mf] full mf_type]} { + puts "Error: Unable to get the type" + } + if {![regexp {\*\n([^ \n]*)} [dump s_tf] full tf_type]} { + puts "Error: Unable to get the type" + } + + if {$mf_type != $tf_type} { + puts "Error: Twins have different geometries" + } + + # get twins for edges of the face + foreach e [explode mf e] { + periodictwins twins $e + checknbshapes twins -edge 2 -m "Periodic twins" -t + mkcurve c_$e $e + if {![regexp {Basis curve :\n([^ \n]*)} [dump c_$e] full e_type]} { + puts "Error: Unable to get the type" + } + + foreach t [explode twins e] { + mkcurve c_$t $t + if {![regexp {Basis curve :\n([^ \n]*)} [dump c_$t] full t_type]} { + puts "Error: Unable to get the type" + } + if {$e_type != $t_type} { + puts "Error: Twins have different geometries" + } + } + } +} + diff --git a/tests/bugs/modalg_7/bug29502 b/tests/bugs/modalg_7/bug29502 index f4d0770a5e..8c5d9003a8 100644 --- a/tests/bugs/modalg_7/bug29502 +++ b/tests/bugs/modalg_7/bug29502 @@ -18,11 +18,12 @@ baddobjects f baddtools v bfillds bsplit r -explode r f + +checknbshapes r -vertex 3 -edge 4 -wire 1 -face 1 # perform unification of the seam edge: # the split vertex should be removed -unifysamedom result r_1 +unifysamedom result r checkshape result checkprops result -equal f