mirror of
https://git.dev.opencascade.org/repos/occt.git
synced 2025-08-09 13:22:24 +03:00
Win8 memory issue fixed.
This commit is contained in:
@@ -13,179 +13,258 @@
|
||||
// Alternatively, this file may be used under the terms of Open CASCADE
|
||||
// commercial license or contractual agreement.
|
||||
|
||||
#ifndef __BLOCK_H__
|
||||
#define __BLOCK_H__
|
||||
#ifndef _BRepMesh_Block_HeaderFile
|
||||
#define _BRepMesh_Block_HeaderFile
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdexcept>
|
||||
|
||||
template <class Type> class Block
|
||||
template <class Type> class Storage
|
||||
{
|
||||
public:
|
||||
|
||||
Block (int theSize, void (*theErrFunction) (char*) = NULL)
|
||||
Storage (int theSize)
|
||||
{
|
||||
myFirst = myLast = NULL;
|
||||
myBlockSize = theSize;
|
||||
myErrorFun = theErrFunction;
|
||||
m_first = m_last = NULL;
|
||||
m_BlockSize = theSize;
|
||||
}
|
||||
|
||||
~Block()
|
||||
~Storage()
|
||||
{
|
||||
while (myFirst)
|
||||
while (m_first)
|
||||
{
|
||||
BlockStruct* aNext = myFirst->Next;
|
||||
delete myFirst;
|
||||
myFirst = aNext;
|
||||
Block* next = m_first->Next;
|
||||
delete[] ( (char*) m_first);
|
||||
m_first = next;
|
||||
}
|
||||
}
|
||||
|
||||
Type* New (int theNum = 1)
|
||||
Type* New (int aNum = 1)
|
||||
{
|
||||
Type* aLast;
|
||||
Type* aResult;
|
||||
|
||||
if (!myLast || myLast->Current + theNum > myLast->Last)
|
||||
if (!m_last || m_last->Current + aNum > m_last->Last)
|
||||
{
|
||||
if (myLast && myLast->Next) { myLast = myLast->Next; }
|
||||
if (m_last && m_last->Next)
|
||||
{
|
||||
m_last = m_last->Next;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
BlockStruct* aNext = (BlockStruct*) new char [sizeof (BlockStruct) + (myBlockSize-1) *sizeof (Type)];
|
||||
Block* aNext = (Block*) new char [sizeof (Block) + (m_BlockSize - 1) *sizeof (Type)];
|
||||
|
||||
if (!aNext) { if (myErrorFun) { (*myErrorFun) ("Not enough memory!"); } exit (1); }
|
||||
if (!aNext)
|
||||
{
|
||||
throw std::runtime_error ("Not enough memory!");
|
||||
}
|
||||
|
||||
if (myLast) { myLast->Next = aNext; }
|
||||
else { myFirst = aNext; }
|
||||
if (m_last)
|
||||
{
|
||||
m_last->Next = aNext;
|
||||
}
|
||||
|
||||
myLast = aNext;
|
||||
myLast->Current = & (myLast->Data[0]);
|
||||
myLast->Last = myLast->Current + myBlockSize;
|
||||
myLast->Next = NULL;
|
||||
else
|
||||
{
|
||||
m_first = aNext;
|
||||
}
|
||||
|
||||
m_last = aNext;
|
||||
m_last->Current = & (m_last->Data[0]);
|
||||
m_last->Last = m_last->Current + m_BlockSize;
|
||||
m_last->Next = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
aLast = myLast->Current;
|
||||
myLast->Current += theNum;
|
||||
return aLast;
|
||||
aResult = m_last->Current;
|
||||
m_last->Current += aNum;
|
||||
return aResult;
|
||||
}
|
||||
|
||||
Type* ScanFirst()
|
||||
{
|
||||
myScanCurrentBlock = myFirst;
|
||||
for (m_scanCurrentBlock = m_first; m_scanCurrentBlock; m_scanCurrentBlock = m_scanCurrentBlock->Next)
|
||||
{
|
||||
m_scanCurrentData = & (m_scanCurrentBlock->Data[0]);
|
||||
|
||||
if (!myScanCurrentBlock) { return NULL; }
|
||||
if (m_scanCurrentData < m_scanCurrentBlock->Current)
|
||||
{
|
||||
return m_scanCurrentData ++;
|
||||
}
|
||||
}
|
||||
|
||||
myScanCurrentData = & (myScanCurrentBlock->Data[0]);
|
||||
return myScanCurrentData++;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Type* ScanNext()
|
||||
{
|
||||
if (myScanCurrentData >= myScanCurrentBlock->Current)
|
||||
while (m_scanCurrentData >= m_scanCurrentBlock->Current)
|
||||
{
|
||||
myScanCurrentBlock = myScanCurrentBlock->Next;
|
||||
m_scanCurrentBlock = m_scanCurrentBlock->Next;
|
||||
|
||||
if (!myScanCurrentBlock) { return NULL; }
|
||||
if (!m_scanCurrentBlock)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
myScanCurrentData = & (myScanCurrentBlock->Data[0]);
|
||||
m_scanCurrentData = & (m_scanCurrentBlock->Data[0]);
|
||||
}
|
||||
|
||||
return myScanCurrentData++;
|
||||
return m_scanCurrentData++;
|
||||
}
|
||||
|
||||
struct BlockIterator;
|
||||
Type* ScanFirst (BlockIterator& anIter)
|
||||
{
|
||||
for (anIter.ScanCurrentBlock = m_first; anIter.ScanCurrentBlock; anIter.ScanCurrentBlock = anIter.ScanCurrentBlock->Next)
|
||||
{
|
||||
anIter.ScanCurrentData = & (anIter.ScanCurrentBlock->Data[0]);
|
||||
|
||||
if (anIter.ScanCurrentData < anIter.ScanCurrentBlock->Current)
|
||||
{
|
||||
return anIter.ScanCurrentData ++;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
Type* ScanNext (BlockIterator& anIter)
|
||||
{
|
||||
while (anIter.ScanCurrentData >= anIter.ScanCurrentBlock->Current)
|
||||
{
|
||||
anIter.ScanCurrentBlock = anIter.ScanCurrentBlock->Next;
|
||||
|
||||
if (!anIter.ScanCurrentBlock)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
anIter.ScanCurrentData = & (anIter.ScanCurrentBlock->Data[0]);
|
||||
}
|
||||
|
||||
return anIter.ScanCurrentData ++;
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
BlockStruct* aBlock;
|
||||
Block* aBlock;
|
||||
|
||||
if (!myFirst) { return; }
|
||||
if (!m_first)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (aBlock = myFirst; ; aBlock = aBlock->Next)
|
||||
for (aBlock = m_first; ; aBlock = aBlock->Next)
|
||||
{
|
||||
aBlock->Current = & (aBlock->Data[0]);
|
||||
|
||||
if (aBlock == myLast) { break; }
|
||||
if (aBlock == m_last)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
myLast = myFirst;
|
||||
m_last = m_first;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
typedef struct BlockSt
|
||||
typedef struct BlockStruct
|
||||
{
|
||||
Type* Current;
|
||||
Type* Last;
|
||||
struct BlockSt* Next;
|
||||
Type* Current, *Last;
|
||||
struct BlockStruct* Next;
|
||||
Type Data[1];
|
||||
} BlockStruct;
|
||||
} Block;
|
||||
|
||||
int myBlockSize;
|
||||
BlockStruct* myFirst;
|
||||
BlockStruct* myLast;
|
||||
|
||||
BlockStruct* myScanCurrentBlock;
|
||||
Type* myScanCurrentData;
|
||||
|
||||
void (*myErrorFun) (char*);
|
||||
};
|
||||
|
||||
template <class Type> class DBlock
|
||||
{
|
||||
int m_BlockSize;
|
||||
Block* m_first;
|
||||
Block* m_last;
|
||||
public:
|
||||
|
||||
DBlock (int theSize, void (*theErrFunction) (char*) = NULL) { myFirst = NULL; myFirstFree = NULL; myBLockSize = theSize; myErrFun = theErrFunction; }
|
||||
struct BlockIterator
|
||||
{
|
||||
Block* ScanCurrentBlock;
|
||||
Type* ScanCurrentData;
|
||||
};
|
||||
|
||||
~DBlock() { while (myFirst) { BlockStruct* aNext = myFirst->Next; delete myFirst; myFirst = aNext; } }
|
||||
private:
|
||||
Block* m_scanCurrentBlock;
|
||||
Type* m_scanCurrentData;
|
||||
};
|
||||
|
||||
template <class Type> class DataBlock
|
||||
{
|
||||
public:
|
||||
DataBlock (int size)
|
||||
{
|
||||
m_first = NULL;
|
||||
m_firstFree = NULL;
|
||||
m_blockSize = size;
|
||||
}
|
||||
|
||||
~DataBlock()
|
||||
{
|
||||
while (m_first)
|
||||
{
|
||||
Block* next = m_first->Next;
|
||||
delete[] ( (char*) m_first);
|
||||
m_first = next;
|
||||
}
|
||||
}
|
||||
|
||||
Type* New()
|
||||
{
|
||||
BlockItem* anItem;
|
||||
|
||||
if (!myFirstFree)
|
||||
if (!m_firstFree)
|
||||
{
|
||||
BlockStruct* next = myFirst;
|
||||
myFirst = (BlockStruct*) new char [sizeof (BlockStruct) + (myBLockSize-1) *sizeof (BlockItem)];
|
||||
Block* next = m_first;
|
||||
m_first = (Block*) new char [sizeof (Block) + (m_blockSize - 1) *sizeof (BlockItem)];
|
||||
|
||||
if (!myFirst) { if (myErrFun) { (*myErrFun) ("Not enough memory!"); } exit (1); }
|
||||
if (!m_first)
|
||||
{
|
||||
throw std::runtime_error ("Not enough memory!");
|
||||
}
|
||||
|
||||
myFirstFree = & (myFirst->Data[0]);
|
||||
m_firstFree = & (m_first->Data[0]);
|
||||
|
||||
for (anItem=myFirstFree; anItem<myFirstFree+myBLockSize-1; anItem++)
|
||||
{ anItem->NextFree = anItem + 1; }
|
||||
for (anItem = m_firstFree; anItem < m_firstFree + m_blockSize - 1; anItem++)
|
||||
{
|
||||
anItem->NextFree = anItem + 1;
|
||||
}
|
||||
|
||||
anItem->NextFree = NULL;
|
||||
myFirst->Next = next;
|
||||
m_first->Next = next;
|
||||
}
|
||||
|
||||
anItem = myFirstFree;
|
||||
myFirstFree = anItem->NextFree;
|
||||
anItem = m_firstFree;
|
||||
m_firstFree = anItem->NextFree;
|
||||
return (Type*) anItem;
|
||||
}
|
||||
|
||||
void Delete (Type* t)
|
||||
{
|
||||
( (BlockItem*) t)->NextFree = myFirstFree;
|
||||
myFirstFree = (BlockItem*) t;
|
||||
( (BlockItem*) t)->NextFree = m_firstFree;
|
||||
m_firstFree = (BlockItem*) t;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
typedef union BlockItemSt
|
||||
typedef union BlockItemStruct
|
||||
{
|
||||
Type Item;
|
||||
BlockItemSt* NextFree;
|
||||
Type Val;
|
||||
BlockItemStruct* NextFree;
|
||||
} BlockItem;
|
||||
|
||||
typedef struct BlockSt
|
||||
typedef struct BlockStruct
|
||||
{
|
||||
struct BlockSt* Next;
|
||||
BlockItem Data[1];
|
||||
} BlockStruct;
|
||||
struct BlockStruct* Next;
|
||||
BlockItem Data[1];
|
||||
} Block;
|
||||
|
||||
int myBLockSize;
|
||||
BlockStruct* myFirst;
|
||||
BlockItem* myFirstFree;
|
||||
|
||||
void (*myErrFun) (char*);
|
||||
int m_blockSize;
|
||||
Block* m_first;
|
||||
BlockItem* m_firstFree;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif // _BRepMesh_Block_HeaderFile
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -13,151 +13,92 @@
|
||||
// Alternatively, this file may be used under the terms of Open CASCADE
|
||||
// commercial license or contractual agreement.
|
||||
|
||||
#ifndef __GRAPH_H__
|
||||
#define __GRAPH_H__
|
||||
#ifndef _BRepMesh_MinStCut_HeaderFile
|
||||
#define _BRepMesh_MinStCut_HeaderFile
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <assert.h>
|
||||
#include <BRepMesh_Block.hxx>
|
||||
|
||||
#define NODE_BLOCK_SIZE 512
|
||||
#define ARC_BLOCK_SIZE 1024
|
||||
#define EDGE_BLOCK_SIZE 1024
|
||||
#define NODEPTR_BLOCK_SIZE 128
|
||||
|
||||
class Graph
|
||||
class GraphEval
|
||||
{
|
||||
public:
|
||||
typedef enum
|
||||
{
|
||||
SOURCE = 0,
|
||||
SINK = 1
|
||||
SOURCE = 0,
|
||||
SINK = 1
|
||||
} TerminalType;
|
||||
|
||||
typedef int NodeId;
|
||||
typedef double CapacityType;
|
||||
typedef double FlowType;
|
||||
typedef void* NodeId;
|
||||
typedef double TCapacityType;
|
||||
|
||||
Graph (void (*theErrFun) (char*) = NULL);
|
||||
GraphEval (const int theNodeNumMax);
|
||||
|
||||
~Graph();
|
||||
|
||||
NodeId AddNode();
|
||||
|
||||
void AddEdge (NodeId theFromNode, NodeId theToNode, CapacityType theCapNode, CapacityType theRevCapacity);
|
||||
|
||||
void SetTWeights (NodeId theNode, CapacityType theCapacityToSource, CapacityType theCapacityToSink);
|
||||
|
||||
void AddTWeights (NodeId theNode, CapacityType theCapacityToSource, CapacityType theCapacityToSink);
|
||||
|
||||
TerminalType Label (NodeId i);
|
||||
~GraphEval();
|
||||
|
||||
NodeId AddNode (int theNum = 1);
|
||||
void AddEdge (NodeId theNodeFrom, NodeId theNodeTo, CapacityType theCap, CapacityType theRevCap);
|
||||
void AddTWeights (NodeId theNode, CapacityType theCapSource, CapacityType theCapSink);
|
||||
TerminalType Label (NodeId theNode);
|
||||
FlowType MaximumFlow();
|
||||
|
||||
private:
|
||||
struct EdgeStruct;
|
||||
|
||||
struct ArcForwardSt;
|
||||
struct ArcReverseSt;
|
||||
|
||||
#define IS_ODD(a) ((int)(a) & 1)
|
||||
#define MAKE_ODD(a) ((ArcForward *) ((int)(a) | 1))
|
||||
#define MAKE_EVEN(a) ((ArcForward *) ((int)(a) & (~1)))
|
||||
#define MAKE_ODD_REV(a) ((ArcReverse *) ((int)(a) | 1))
|
||||
#define MAKE_EVEN_REV(a) ((ArcReverse *) ((int)(a) & (~1)))
|
||||
|
||||
typedef struct NodeSt
|
||||
typedef struct NodeStruct
|
||||
{
|
||||
ArcForwardSt* FirstOut;
|
||||
ArcReverseSt* FirstIn;
|
||||
EdgeStruct* First;
|
||||
|
||||
ArcForwardSt* Parent;
|
||||
EdgeStruct* Parent;
|
||||
NodeStruct* Next;
|
||||
int TS;
|
||||
int DIST;
|
||||
short IsSink;
|
||||
|
||||
NodeSt* Next;
|
||||
|
||||
int TimeStamp;
|
||||
int Distance;
|
||||
short IsSink;
|
||||
|
||||
CapacityType TrCapacity;
|
||||
CapacityType TrCap;
|
||||
} Node;
|
||||
|
||||
#define NEIGHBOR_NODE(i, shift) ((Node *) ((char *)(i) + (shift)))
|
||||
#define NEIGHBOR_NODE_REV(i, shift) ((Node *) ((char *)(i) - (shift)))
|
||||
typedef struct ArcForwardSt
|
||||
typedef struct EdgeStruct
|
||||
{
|
||||
int Shift;
|
||||
CapacityType ResidualCap;
|
||||
CapacityType ReverseResidualCap;
|
||||
} ArcForward;
|
||||
NodeStruct* Head;
|
||||
EdgeStruct* Next;
|
||||
EdgeStruct* Sister;
|
||||
|
||||
typedef struct ArcReverseSt
|
||||
{
|
||||
ArcForward* Sister;
|
||||
} ArcReverse;
|
||||
CapacityType ReverseCap;
|
||||
} Edge;
|
||||
|
||||
typedef struct NodePtrSt
|
||||
typedef struct NodePtrStruct
|
||||
{
|
||||
NodeSt* Ptr;
|
||||
NodePtrSt* Next;
|
||||
NodeStruct* Ptr;
|
||||
NodePtrStruct* Next;
|
||||
} NodePtr;
|
||||
|
||||
typedef struct NodeBLockSt
|
||||
{
|
||||
Node* Current;
|
||||
struct NodeBLockSt* Next;
|
||||
Node Nodes[NODE_BLOCK_SIZE];
|
||||
} NodeBlock;
|
||||
NodeStruct* m_nodes;
|
||||
int m_nodeNum, m_nodeNumMax;
|
||||
Storage<Edge>* m_edgeBlock;
|
||||
DataBlock<NodePtr>* m_nodePtrBlock;
|
||||
|
||||
#define last_node LAST_NODE.LAST_NODE
|
||||
FlowType m_flow;
|
||||
|
||||
typedef struct ArcForBlockSt
|
||||
{
|
||||
char* Start;
|
||||
ArcForward* Current;
|
||||
struct ArcForBlockSt* Next;
|
||||
ArcForward ArcsFor[ARC_BLOCK_SIZE];
|
||||
union
|
||||
{
|
||||
ArcForward Dummy;
|
||||
Node* LAST_NODE;
|
||||
} LAST_NODE;
|
||||
} ArcForBlock;
|
||||
|
||||
typedef struct ArcRevBlockSt
|
||||
{
|
||||
char* Start;
|
||||
ArcReverse* Current;
|
||||
struct ArcRevBlockSt* Next;
|
||||
ArcReverse ArcsRev[ARC_BLOCK_SIZE];
|
||||
union
|
||||
{
|
||||
ArcReverse Dummy;
|
||||
Node* LAST_NODE;
|
||||
} LAST_NODE;
|
||||
} ArcRevBlock;
|
||||
|
||||
NodeBlock* myNodeBlockFirst;
|
||||
ArcForBlock* myArcForBlockFirst;
|
||||
ArcRevBlock* myArcREvBlockFirst;
|
||||
DBlock<NodePtr>* myNodePtrBLock;
|
||||
|
||||
void (*myErrorFun) (char*);
|
||||
|
||||
FlowType myFlow;
|
||||
|
||||
Node* myQueueFirst[2], *myQueueLast[2];
|
||||
NodePtr* myOrphanFirst, *myOrphanLast;
|
||||
int myTime;
|
||||
Node* m_queueFirst[2], *m_queueLast[2];
|
||||
NodePtr* m_orphanFirst, *m_orphanLast;
|
||||
int m_time;
|
||||
|
||||
void setActive (Node* theNode);
|
||||
Node* nextActive();
|
||||
|
||||
void prepareGraph();
|
||||
void maxflowInit();
|
||||
void augment (Node* theSStart, Node* theTStart, CapacityType* theCapMiddle, CapacityType* theRevCapMiddle);
|
||||
void augment (Edge* theMiddleEdge);
|
||||
void processSourceOrphan (Node* theNode);
|
||||
void processSinkOrphan (Node* theNode);
|
||||
};
|
||||
|
||||
class Energy : Graph
|
||||
class Energy : GraphEval
|
||||
{
|
||||
public:
|
||||
typedef NodeId Var;
|
||||
@@ -165,7 +106,7 @@ public:
|
||||
typedef CapacityType Value;
|
||||
typedef FlowType TotalValue;
|
||||
|
||||
Energy (void (*theErrFun) (char*) = NULL);
|
||||
Energy (const int theMaxNodes);
|
||||
|
||||
~Energy();
|
||||
|
||||
@@ -184,23 +125,19 @@ public:
|
||||
|
||||
int Label (Var x);
|
||||
|
||||
private:
|
||||
|
||||
TotalValue myEConst;
|
||||
void (*myErrorFun) (char*);
|
||||
};
|
||||
|
||||
inline Energy::Energy (void (*err_function) (char*)) : Graph (err_function)
|
||||
inline Energy::Energy (const int theMaxNodes)
|
||||
: GraphEval (theMaxNodes)
|
||||
{
|
||||
myEConst = 0;
|
||||
myErrorFun = err_function;
|
||||
}
|
||||
|
||||
inline Energy::~Energy() {}
|
||||
|
||||
inline Energy::Var Energy::AddVariable() { return AddNode(); }
|
||||
|
||||
inline void Energy::AddConstant (Value A) { myEConst += A; }
|
||||
inline Energy::Var Energy::AddVariable()
|
||||
{
|
||||
return AddNode();
|
||||
}
|
||||
|
||||
inline void Energy::AddTerm (Var x,
|
||||
Value A, Value B)
|
||||
@@ -222,22 +159,24 @@ inline void Energy::AddPairwise (Var x, Var y,
|
||||
{
|
||||
AddTWeights (x, 0, B);
|
||||
AddTWeights (y, 0, -B);
|
||||
AddEdge (x, y, 0, B+C);
|
||||
AddEdge (x, y, 0, B + C);
|
||||
}
|
||||
|
||||
else if (C < 0)
|
||||
{
|
||||
AddTWeights (x, 0, -C);
|
||||
AddTWeights (y, 0, C);
|
||||
AddEdge (x, y, B+C, 0);
|
||||
AddEdge (x, y, B + C, 0);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
AddEdge (x, y, B, C);
|
||||
}
|
||||
}
|
||||
|
||||
inline Energy::TotalValue Energy::Minimize() { return myEConst + MaximumFlow(); }
|
||||
inline Energy::TotalValue Energy::Minimize() { return MaximumFlow(); }
|
||||
|
||||
inline int Energy::Label (Var x) { return (int) Graph::Label (x); }
|
||||
inline int Energy::Label (Var x) { return (int) GraphEval::Label (x); }
|
||||
|
||||
#endif
|
||||
#endif // _BRepMesh_MinStCut_HeaderFile
|
||||
|
@@ -789,7 +789,7 @@ void BRepMesh_RestoreOrientationTool::Perform()
|
||||
#endif
|
||||
|
||||
// Optimization
|
||||
Energy* aGraph = new Energy();
|
||||
Energy* aGraph = new Energy (myPatches.size());
|
||||
|
||||
std::vector<Energy::Var> aVariables (myPatches.size());
|
||||
|
||||
@@ -857,19 +857,6 @@ void BRepMesh_RestoreOrientationTool::Perform()
|
||||
std::cout << "Optimization time: " << aTimer.ElapsedTime() << std::endl;
|
||||
#endif
|
||||
|
||||
Handle (BRepMesh_TriangulatedPatch) aMergedPatch = new BRepMesh_TriangulatedPatch(myFaceList[0]);
|
||||
for (Standard_Size i = 0; i < myPatches.size(); ++i)
|
||||
{
|
||||
Handle (BRepMesh_TriangulatedPatch)& aTriPatch = myPatches[i];
|
||||
|
||||
if (aFlipped[i])
|
||||
{
|
||||
aTriPatch->Flip();
|
||||
}
|
||||
|
||||
aMergedPatch->Append (aTriPatch);
|
||||
}
|
||||
|
||||
BRep_Builder aBuilder;
|
||||
TopoDS_Compound aCompound;
|
||||
aBuilder.MakeCompound (aCompound);
|
||||
|
Reference in New Issue
Block a user