1
0
mirror of https://git.dev.opencascade.org/repos/occt.git synced 2025-04-03 17:56:21 +03:00

0029325: Modeling Algorithms - add tool BRepLib_PointCloudShape for generation point cloud for specified shape

Added PLY writing tools RWPly_CafWriter and RWPly_PlyWriterContext.

Added tool BRepLib_PointCloudShape generating point cloud from shape in two ways:
- random points on surface with specified density;
- points from triangulation nodes.

StdPrs_ToolTriangulatedShape::ComputeNormals() has been moved to
BRepLib_ToolTriangulatedShape for reusing outside of AIS.

Command vpointcloud has been extended to use new generation tool.
Command writeply has been added to write triangulation or point set into PLY format.
This commit is contained in:
gka 2017-11-08 17:13:53 +03:00 committed by smoskvin
parent ae38730d35
commit e2d60d0f7f
20 changed files with 2017 additions and 258 deletions

View File

@ -454,6 +454,7 @@ t TKRWMesh
n RWGltf
n RWMesh
n RWObj
n RWPly
n DFBrowser
n DFBrowserPane
n DFBrowserPaneXDE

View File

@ -0,0 +1,307 @@
// Copyright (c) 2021 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 <BRepLib_PointCloudShape.hxx>
#include <BRep_Tool.hxx>
#include <BRepGProp.hxx>
#include <BRepLib_ToolTriangulatedShape.hxx>
#include <BRepTools.hxx>
#include <BRepTopAdaptor_FClass2d.hxx>
#include <Geom_Surface.hxx>
#include <GProp_GProps.hxx>
#include <gp_Pnt.hxx>
#include <gp_Vec.hxx>
#include <Precision.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Shape.hxx>
#include <random>
// =======================================================================
// function : BRepLib_PointCloudShape
// purpose :
// =======================================================================
BRepLib_PointCloudShape::BRepLib_PointCloudShape (const TopoDS_Shape& theShape,
const Standard_Real theTol)
: myShape (theShape),
myDist (0.0),
myTol (theTol),
myNbPoints (0)
{
//
}
// =======================================================================
// function : ~BRepLib_PointCloudShape
// purpose :
// =======================================================================
BRepLib_PointCloudShape::~BRepLib_PointCloudShape()
{
//
}
// =======================================================================
// function : NbPointsByDensity
// purpose :
// =======================================================================
Standard_Integer BRepLib_PointCloudShape::NbPointsByDensity (const Standard_Real theDensity)
{
clear();
Standard_Real aDensity = (theDensity < Precision::Confusion() ? computeDensity() : theDensity);
if (aDensity < Precision::Confusion())
{
return 0;
}
Standard_Integer aNbPoints = 0;
for (TopExp_Explorer aExpF(myShape, TopAbs_FACE); aExpF.More(); aExpF.Next())
{
Standard_Real anArea = faceArea(aExpF.Current());
Standard_Integer aNbPnts = Max ((Standard_Integer)std::ceil(anArea / theDensity), 1);
myFacePoints.Bind(aExpF.Current(), aNbPnts);
aNbPoints+= aNbPnts;
}
return aNbPoints;
}
// =======================================================================
// function : GeneratePointsByDensity
// purpose :
// =======================================================================
Standard_Boolean BRepLib_PointCloudShape::GeneratePointsByDensity (const Standard_Real theDensity)
{
if (myFacePoints.IsEmpty())
{
if (NbPointsByDensity (theDensity) == 0)
{
return Standard_False;
}
}
Standard_Integer aNbAdded = 0;
for (TopExp_Explorer aExpF (myShape, TopAbs_FACE); aExpF.More(); aExpF.Next())
{
if (addDensityPoints (aExpF.Current()))
{
aNbAdded++;
}
}
return (aNbAdded > 0);
}
// =======================================================================
// function : GeneratePointsByTriangulation
// purpose :
// =======================================================================
Standard_Boolean BRepLib_PointCloudShape::GeneratePointsByTriangulation()
{
clear();
Standard_Integer aNbAdded = 0;
for (TopExp_Explorer aExpF (myShape, TopAbs_FACE); aExpF.More(); aExpF.Next())
{
if (addTriangulationPoints (aExpF.Current()))
{
aNbAdded++;
}
}
return (aNbAdded > 0);
}
// =======================================================================
// function : faceArea
// purpose :
// =======================================================================
Standard_Real BRepLib_PointCloudShape::faceArea (const TopoDS_Shape& theShape)
{
Standard_Real anArea = 0.0;
if (myFaceArea.Find (theShape, anArea))
{
return anArea;
}
GProp_GProps aFaceProps;
BRepGProp::SurfaceProperties (theShape, aFaceProps);
anArea = aFaceProps.Mass();
myFaceArea.Bind (theShape, anArea);
return anArea;
}
// =======================================================================
// function : computeDensity
// purpose :
// =======================================================================
Standard_Real BRepLib_PointCloudShape::computeDensity()
{
// at first step find the face with smallest area
Standard_Real anAreaMin = Precision::Infinite();
for (TopExp_Explorer aExpF (myShape, TopAbs_FACE); aExpF.More(); aExpF.Next())
{
Standard_Real anArea = faceArea (aExpF.Current());
if (anArea < myTol * myTol)
{
continue;
}
if (anArea < anAreaMin)
{
anAreaMin = anArea;
}
}
return anAreaMin * 0.1;
}
// =======================================================================
// function : NbPointsByTriangulation
// purpose :
// =======================================================================
Standard_Integer BRepLib_PointCloudShape::NbPointsByTriangulation() const
{
// at first step find the face with smallest area
Standard_Integer aNbPoints = 0;
for (TopExp_Explorer aExpF (myShape, TopAbs_FACE); aExpF.More(); aExpF.Next())
{
TopLoc_Location aLoc;
Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation (TopoDS::Face (aExpF.Current()), aLoc);
if (aTriangulation.IsNull())
{
continue;
}
aNbPoints += aTriangulation->NbNodes();
}
return aNbPoints;
}
// =======================================================================
// function : addDensityPoints
// purpose :
// =======================================================================
Standard_Boolean BRepLib_PointCloudShape::addDensityPoints (const TopoDS_Shape& theFace)
{
//addition of the points with specified density on the face by random way
Standard_Integer aNbPnts = (myFacePoints.IsBound (theFace) ? myFacePoints.Find (theFace) : 0);
if (aNbPnts == 0)
{
return Standard_False;
}
TopoDS_Face aFace = TopoDS::Face (theFace);
Standard_Real anUMin = 0.0, anUMax = 0.0, aVMin = 0.0, aVMax = 0.0;
BRepTools::UVBounds (aFace, anUMin, anUMax, aVMin, aVMax);
BRepTopAdaptor_FClass2d aClassifier (aFace, Precision::Confusion());
TopLoc_Location aLoc = theFace.Location();
const gp_Trsf& aTrsf = aLoc.Transformation();
TopLoc_Location aLoc1;
Handle(Geom_Surface) aSurf = BRep_Tool::Surface (aFace, aLoc1);
if (aSurf.IsNull())
{
return Standard_False;
}
std::mt19937 aRandomGenerator(0);
std::uniform_real_distribution<> anUDistrib(anUMin, anUMax);
std::uniform_real_distribution<> aVDistrib (aVMin, aVMax);
for (Standard_Integer nbCurPnts = 1; nbCurPnts <= aNbPnts;)
{
const Standard_Real aU = anUDistrib(aRandomGenerator);
const Standard_Real aV = aVDistrib (aRandomGenerator);
gp_Pnt2d aUVNode (aU, aV);
const TopAbs_State aState = aClassifier.Perform (aUVNode);
if (aState == TopAbs_OUT)
{
continue;
}
nbCurPnts++;
gp_Pnt aP1;
gp_Vec dU, dV;
aSurf->D1 (aU, aV, aP1, dU, dV);
gp_Vec aNorm = dU ^ dV;
if (aFace.Orientation() == TopAbs_REVERSED)
{
aNorm.Reverse();
}
const Standard_Real aNormMod = aNorm.Magnitude();
if (aNormMod > gp::Resolution())
{
aNorm /= aNormMod;
}
if (myDist > Precision::Confusion())
{
std::uniform_real_distribution<> aDistanceDistrib (0.0, myDist);
gp_XYZ aDeflPoint = aP1.XYZ() + aNorm.XYZ() * aDistanceDistrib (aRandomGenerator);
aP1.SetXYZ (aDeflPoint);
}
aP1.Transform (aTrsf);
if (aNormMod > gp::Resolution())
{
aNorm = gp_Dir (aNorm).Transformed (aTrsf);
}
addPoint (aP1, aNorm, aUVNode, aFace);
}
return Standard_True;
}
// =======================================================================
// function : addTriangulationPoints
// purpose :
// =======================================================================
Standard_Boolean BRepLib_PointCloudShape::addTriangulationPoints (const TopoDS_Shape& theFace)
{
TopLoc_Location aLoc;
TopoDS_Face aFace = TopoDS::Face (theFace);
Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation (aFace, aLoc);
if (aTriangulation.IsNull())
{
return Standard_False;
}
TopLoc_Location aLoc1;
Handle(Geom_Surface) aSurf = BRep_Tool::Surface (aFace, aLoc1);
const gp_Trsf& aTrsf = aLoc.Transformation();
BRepLib_ToolTriangulatedShape::ComputeNormals (aFace, aTriangulation);
Standard_Boolean aHasUVNode = aTriangulation->HasUVNodes();
for (Standard_Integer aNodeIter = 1; aNodeIter <= aTriangulation->NbNodes(); ++aNodeIter)
{
gp_Pnt aP1 = aTriangulation->Node (aNodeIter);
gp_Dir aNormal = aTriangulation->Normal(aNodeIter);
if (!aLoc.IsIdentity())
{
aP1 .Transform (aTrsf);
aNormal.Transform (aTrsf);
}
const gp_Pnt2d anUVNode = aHasUVNode ? aTriangulation->UVNode (aNodeIter) : gp_Pnt2d();
addPoint (aP1, aNormal, anUVNode, aFace);
}
return Standard_True;
}
// =======================================================================
// function : clear
// purpose :
// =======================================================================
void BRepLib_PointCloudShape::clear()
{
myFaceArea.Clear();
myFacePoints.Clear();
}

View File

@ -0,0 +1,116 @@
// Copyright (c) 2021 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 _BRepLib_PointCloudShape_HeaderFile
#define _BRepLib_PointCloudShape_HeaderFile
#include <TopTools_DataMapOfShapeInteger.hxx>
#include <TopTools_DataMapOfShapeReal.hxx>
#include <Quantity_Color.hxx>
#include <Precision.hxx>
//! This tool is intended to get points from shape with specified distance from shape along normal.
//! Can be used to simulation of points obtained in result of laser scan of shape.
//! There are 2 ways for generation points by shape:
//! 1. Generation points with specified density
//! 2. Generation points using triangulation Nodes
//! Generation of points by density using the GeneratePointsByDensity() function is not thread safe.
class BRepLib_PointCloudShape
{
public:
DEFINE_STANDARD_ALLOC
//! Constructor initialized by shape
Standard_EXPORT BRepLib_PointCloudShape (const TopoDS_Shape& theShape = TopoDS_Shape(),
const Standard_Real theTol = Precision::Confusion());
//! Virtual destructor
Standard_EXPORT virtual ~BRepLib_PointCloudShape();
//! Return loaded shape.
const TopoDS_Shape& Shape() const { return myShape; }
//! Set shape.
void SetShape (const TopoDS_Shape& theShape) { myShape = theShape; }
//! Return tolerance.
Standard_Real Tolerance() const { return myTol; }
//! Set tolerance.
void SetTolerance (Standard_Real theTol) { myTol = theTol; }
//! Returns value of the distance to define deflection of points from shape along normal to shape; 0.0 by default.
Standard_Real GetDistance() const { return myDist; }
//! Sets value of the distance to define deflection of points from shape along normal to shape.
//! Negative values of theDist parameter are ignored.
void SetDistance (const Standard_Real theDist) { myDist = theDist; }
//! Returns size of the point cloud for specified density.
Standard_EXPORT Standard_Integer NbPointsByDensity (const Standard_Real theDensity = 0.0);
//! Returns size of the point cloud for using triangulation.
Standard_EXPORT Standard_Integer NbPointsByTriangulation() const;
//! Computes points with specified density for initial shape.
//! If parameter Density is equal to 0 then density will be computed automatically by criterion:
//! - 10 points per minimal unreduced face area.
//!
//! Note: this function should not be called from concurrent threads without external lock.
Standard_EXPORT Standard_Boolean GeneratePointsByDensity (const Standard_Real theDensity = 0.0);
//! Get points from triangulation existing in the shape.
Standard_EXPORT Standard_Boolean GeneratePointsByTriangulation();
protected:
//! Compute area of the specified face.
Standard_EXPORT Standard_Real faceArea (const TopoDS_Shape& theShape);
//! Computes default density points per face.
Standard_EXPORT Standard_Real computeDensity();
//! Adds points to face in accordance with the specified density randomly in the specified range [0, Dist].
Standard_EXPORT Standard_Boolean addDensityPoints (const TopoDS_Shape& theFace);
//! Adds points to face by nodes of the existing triangulation randomly in the specified range [0, Dist].
Standard_EXPORT Standard_Boolean addTriangulationPoints (const TopoDS_Shape& theFace);
protected:
//! Method to clear maps.
Standard_EXPORT virtual void clear();
//! Method to add point, normal to surface in this point and face for which point computed.
//! @param[in] thePoint 3D point on the surface
//! @param[in] theNorm surface normal at this point
//! @param[in] theUV surface UV parameters
//! @param[in] theFace surface (face) definition
Standard_EXPORT virtual void addPoint (const gp_Pnt& thePoint,
const gp_Vec& theNorm,
const gp_Pnt2d& theUV,
const TopoDS_Shape& theFace) = 0;
protected:
TopoDS_Shape myShape;
Standard_Real myDist;
Standard_Real myTol;
TopTools_DataMapOfShapeReal myFaceArea;
TopTools_DataMapOfShapeInteger myFacePoints;
Standard_Integer myNbPoints;
};
#endif // _BRepLib_PointCloudShape_HeaderFile

View File

@ -0,0 +1,83 @@
// Copyright (c) 2021 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 <BRepLib_ToolTriangulatedShape.hxx>
#include <BRep_Tool.hxx>
#include <GeomLib.hxx>
#include <Poly.hxx>
#include <Poly_Connect.hxx>
#include <Precision.hxx>
#include <TopLoc_Location.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Face.hxx>
// =======================================================================
// function : ComputeNormals
// purpose :
// =======================================================================
void BRepLib_ToolTriangulatedShape::ComputeNormals (const TopoDS_Face& theFace,
const Handle(Poly_Triangulation)& theTris,
Poly_Connect& thePolyConnect)
{
if (theTris.IsNull()
|| theTris->HasNormals())
{
return;
}
// take in face the surface location
const TopoDS_Face aZeroFace = TopoDS::Face (theFace.Located (TopLoc_Location()));
Handle(Geom_Surface) aSurf = BRep_Tool::Surface (aZeroFace);
if (!theTris->HasUVNodes() || aSurf.IsNull())
{
// compute normals by averaging triangulation normals sharing the same vertex
Poly::ComputeNormals (theTris);
return;
}
const Standard_Real aTol = Precision::Confusion();
Standard_Integer aTri[3];
gp_Dir aNorm;
theTris->AddNormals();
for (Standard_Integer aNodeIter = 1; aNodeIter <= theTris->NbNodes(); ++aNodeIter)
{
// try to retrieve normal from real surface first, when UV coordinates are available
if (GeomLib::NormEstim (aSurf, theTris->UVNode (aNodeIter), aTol, aNorm) > 1)
{
if (thePolyConnect.Triangulation() != theTris)
{
thePolyConnect.Load (theTris);
}
// compute flat normals
gp_XYZ eqPlan (0.0, 0.0, 0.0);
for (thePolyConnect.Initialize (aNodeIter); thePolyConnect.More(); thePolyConnect.Next())
{
theTris->Triangle (thePolyConnect.Value()).Get (aTri[0], aTri[1], aTri[2]);
const gp_XYZ v1 (theTris->Node (aTri[1]).Coord() - theTris->Node (aTri[0]).Coord());
const gp_XYZ v2 (theTris->Node (aTri[2]).Coord() - theTris->Node (aTri[1]).Coord());
const gp_XYZ vv = v1 ^ v2;
const Standard_Real aMod = vv.Modulus();
if (aMod >= aTol)
{
eqPlan += vv / aMod;
}
}
const Standard_Real aModMax = eqPlan.Modulus();
aNorm = (aModMax > aTol) ? gp_Dir (eqPlan) : gp::DZ();
}
theTris->SetNormal (aNodeIter, aNorm);
}
}

View File

@ -0,0 +1,50 @@
// Copyright (c) 2021 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 _BrepLib_ToolTriangulatedShape_HeaderFile
#define _BrepLib_ToolTriangulatedShape_HeaderFile
#include <Poly_Connect.hxx>
#include <Poly_Triangulation.hxx>
class TopoDS_Face;
class Poly_Triangulation;
//! Provides methods for calculating normals to Poly_Triangulation of TopoDS_Face.
class BRepLib_ToolTriangulatedShape
{
public:
//! Computes nodal normals for Poly_Triangulation structure using UV coordinates and surface.
//! Does nothing if triangulation already defines normals.
//! @param[in] theFace the face
//! @param[in] theTris the definition of a face triangulation
static void ComputeNormals (const TopoDS_Face& theFace,
const Handle(Poly_Triangulation)& theTris)
{
Poly_Connect aPolyConnect;
ComputeNormals (theFace, theTris, aPolyConnect);
}
//! Computes nodal normals for Poly_Triangulation structure using UV coordinates and surface.
//! Does nothing if triangulation already defines normals.
//! @param[in] theFace the face
//! @param[in] theTris the definition of a face triangulation
//! @param[in,out] thePolyConnect optional, initialized tool for exploring triangulation
Standard_EXPORT static void ComputeNormals (const TopoDS_Face& theFace,
const Handle(Poly_Triangulation)& theTris,
Poly_Connect& thePolyConnect);
};
#endif

View File

@ -30,8 +30,12 @@ BRepLib_MakeVertex.hxx
BRepLib_MakeWire.cxx
BRepLib_MakeWire.hxx
BRepLib_MakeWire_1.cxx
BRepLib_PointCloudShape.hxx
BRepLib_PointCloudShape.cxx
BRepLib_ShapeModification.hxx
BRepLib_ShellError.hxx
BRepLib_ToolTriangulatedShape.hxx
BRepLib_ToolTriangulatedShape.cxx
BRepLib_ValidateEdge.cxx
BRepLib_ValidateEdge.hxx
BRepLib_WireError.hxx

4
src/RWPly/FILES Normal file
View File

@ -0,0 +1,4 @@
RWPly_CafWriter.cxx
RWPly_CafWriter.hxx
RWPly_PlyWriterContext.cxx
RWPly_PlyWriterContext.hxx

View File

@ -0,0 +1,302 @@
// Copyright (c) 2022 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 <RWPly_CafWriter.hxx>
#include <Message.hxx>
#include <Message_LazyProgressScope.hxx>
#include <OSD_Path.hxx>
#include <RWMesh_FaceIterator.hxx>
#include <RWMesh_MaterialMap.hxx>
#include <RWPly_PlyWriterContext.hxx>
#include <Standard_CLocaleSentry.hxx>
#include <TDocStd_Document.hxx>
#include <TDataStd_Name.hxx>
#include <XCAFDoc_DocumentTool.hxx>
#include <XCAFDoc_ShapeTool.hxx>
#include <XCAFPrs_DocumentExplorer.hxx>
IMPLEMENT_STANDARD_RTTIEXT(RWPly_CafWriter, Standard_Transient)
//================================================================
// Function : Constructor
// Purpose :
//================================================================
RWPly_CafWriter::RWPly_CafWriter (const TCollection_AsciiString& theFile)
: myFile (theFile),
myIsDoublePrec (false),
myHasNormals (true),
myHasColors (true),
myHasTexCoords (false),
myHasPartId (true),
myHasFaceId (false)
{
//
}
//================================================================
// Function : Destructor
// Purpose :
//================================================================
RWPly_CafWriter::~RWPly_CafWriter()
{
//
}
//================================================================
// Function : toSkipFaceMesh
// Purpose :
//================================================================
Standard_Boolean RWPly_CafWriter::toSkipFaceMesh (const RWMesh_FaceIterator& theFaceIter)
{
return theFaceIter.IsEmptyMesh();
}
// =======================================================================
// function : Perform
// purpose :
// =======================================================================
bool RWPly_CafWriter::Perform (const Handle(TDocStd_Document)& theDocument,
const TColStd_IndexedDataMapOfStringString& theFileInfo,
const Message_ProgressRange& theProgress)
{
TDF_LabelSequence aRoots;
Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool (theDocument->Main());
aShapeTool->GetFreeShapes (aRoots);
return Perform (theDocument, aRoots, NULL, theFileInfo, theProgress);
}
// =======================================================================
// function : Perform
// purpose :
// =======================================================================
bool RWPly_CafWriter::Perform (const Handle(TDocStd_Document)& theDocument,
const TDF_LabelSequence& theRootLabels,
const TColStd_MapOfAsciiString* theLabelFilter,
const TColStd_IndexedDataMapOfStringString& theFileInfo,
const Message_ProgressRange& theProgress)
{
TCollection_AsciiString aFolder, aFileName, aFullFileNameBase, aShortFileNameBase, aFileExt;
OSD_Path::FolderAndFileFromPath (myFile, aFolder, aFileName);
OSD_Path::FileNameAndExtension (aFileName, aShortFileNameBase, aFileExt);
Standard_Real aLengthUnit = 1.;
if (XCAFDoc_DocumentTool::GetLengthUnit(theDocument, aLengthUnit))
{
myCSTrsf.SetInputLengthUnit(aLengthUnit);
}
if (theRootLabels.IsEmpty()
|| (theLabelFilter != NULL && theLabelFilter->IsEmpty()))
{
Message::SendFail ("Nothing to export into PLY file");
return false;
}
Standard_Integer aNbNodesAll = 0, aNbElemsAll = 0;
Standard_Real aNbPEntities = 0; // steps for progress range
for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_OnlyLeafNodes);
aDocExplorer.More(); aDocExplorer.Next())
{
const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current();
if (theLabelFilter != NULL
&& !theLabelFilter->Contains (aDocNode.Id))
{
continue;
}
for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, aDocNode.Location, true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next())
{
if (toSkipFaceMesh (aFaceIter))
{
continue;
}
addFaceInfo (aFaceIter, aNbNodesAll, aNbElemsAll);
aNbPEntities += aNbNodesAll + aNbElemsAll;
}
}
if (aNbNodesAll == 0)
{
Message::SendFail ("No mesh data to save");
return false;
}
Standard_CLocaleSentry aLocaleSentry;
RWPly_PlyWriterContext aPlyCtx;
aPlyCtx.SetDoublePrecision (myIsDoublePrec);
aPlyCtx.SetNormals (myHasNormals);
aPlyCtx.SetColors (myHasColors);
aPlyCtx.SetTexCoords (myHasTexCoords);
aPlyCtx.SetSurfaceId (myHasPartId || myHasFaceId);
if (!aPlyCtx.Open (myFile)
|| !aPlyCtx.WriteHeader (aNbNodesAll, aNbElemsAll, theFileInfo))
{
return false;
}
// simple global progress sentry
const Standard_Real aPatchStep = 2048.0;
Message_LazyProgressScope aPSentry (theProgress, "PLY export", aNbPEntities, aPatchStep);
bool isDone = true;
for (Standard_Integer aStepIter = 0; aStepIter < 2; ++aStepIter)
{
aPlyCtx.SetSurfaceId (0);
for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_OnlyLeafNodes);
aDocExplorer.More() && !aPSentry.IsAborted(); aDocExplorer.Next())
{
const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current();
if (theLabelFilter != NULL
&& !theLabelFilter->Contains (aDocNode.Id))
{
continue;
}
if (myHasPartId)
{
aPlyCtx.SetSurfaceId (aPlyCtx.SurfaceId() + 1);
}
if (!writeShape (aPlyCtx, aPSentry, aStepIter, aDocNode.RefLabel, aDocNode.Location, aDocNode.Style))
{
isDone = false;
break;
}
}
}
const bool isClosed = aPlyCtx.Close();
if (isDone && !isClosed)
{
Message::SendFail (TCollection_AsciiString ("Failed to write PLY file\n") + myFile);
return false;
}
return isDone && !aPSentry.IsAborted();
}
// =======================================================================
// function : addFaceInfo
// purpose :
// =======================================================================
void RWPly_CafWriter::addFaceInfo (const RWMesh_FaceIterator& theFace,
Standard_Integer& theNbNodes,
Standard_Integer& theNbElems)
{
theNbNodes += theFace.NbNodes();
theNbElems += theFace.NbTriangles();
}
// =======================================================================
// function : writeShape
// purpose :
// =======================================================================
bool RWPly_CafWriter::writeShape (RWPly_PlyWriterContext& theWriter,
Message_LazyProgressScope& thePSentry,
const Standard_Integer theWriteStep,
const TDF_Label& theLabel,
const TopLoc_Location& theParentTrsf,
const XCAFPrs_Style& theParentStyle)
{
for (RWMesh_FaceIterator aFaceIter (theLabel, theParentTrsf, true, theParentStyle); aFaceIter.More() && !thePSentry.IsAborted(); aFaceIter.Next())
{
if (toSkipFaceMesh (aFaceIter))
{
continue;
}
if (theWriteStep == 0
&& !writeNodes (theWriter, thePSentry, aFaceIter))
{
return false;
}
if (theWriteStep == 1
&& !writeIndices (theWriter, thePSentry, aFaceIter))
{
return false;
}
}
return true;
}
// =======================================================================
// function : writeNodes
// purpose :
// =======================================================================
bool RWPly_CafWriter::writeNodes (RWPly_PlyWriterContext& theWriter,
Message_LazyProgressScope& thePSentry,
const RWMesh_FaceIterator& theFace)
{
const Standard_Integer aNodeUpper = theFace.NodeUpper();
Graphic3d_Vec3 aNormVec;
Graphic3d_Vec2 aTexVec;
Graphic3d_Vec4ub aColorVec (255);
if (theFace.HasFaceColor())
{
//Graphic3d_Vec4 aColorF = Quantity_ColorRGBA::Convert_LinearRGB_To_sRGB (theFace.FaceColor());
Graphic3d_Vec4 aColorF = theFace.FaceColor();
aColorVec.SetValues ((unsigned char )int(aColorF.r() * 255.0f),
(unsigned char )int(aColorF.g() * 255.0f),
(unsigned char )int(aColorF.b() * 255.0f),
(unsigned char )int(aColorF.a() * 255.0f));
}
for (Standard_Integer aNodeIter = theFace.NodeLower(); aNodeIter <= aNodeUpper && thePSentry.More(); ++aNodeIter, thePSentry.Next())
{
gp_XYZ aNode = theFace.NodeTransformed (aNodeIter).XYZ();
myCSTrsf.TransformPosition (aNode);
if (theFace.HasNormals())
{
gp_Dir aNorm = theFace.NormalTransformed (aNodeIter);
aNormVec.SetValues ((float )aNorm.X(), (float )aNorm.Y(), (float )aNorm.Z());
myCSTrsf.TransformNormal (aNormVec);
}
if (theFace.HasTexCoords())
{
const gp_Pnt2d aUV = theFace.NodeTexCoord (aNodeIter);
aTexVec.SetValues ((float )aUV.X(), (float )aUV.Y());
}
if (!theWriter.WriteVertex (aNode, aNormVec, aTexVec, aColorVec))
{
return false;
}
}
return true;
}
// =======================================================================
// function : writeIndices
// purpose :
// =======================================================================
bool RWPly_CafWriter::writeIndices (RWPly_PlyWriterContext& theWriter,
Message_LazyProgressScope& thePSentry,
const RWMesh_FaceIterator& theFace)
{
if (myHasFaceId)
{
theWriter.SetSurfaceId (theWriter.SurfaceId() + 1);
}
const Standard_Integer anElemLower = theFace.ElemLower();
const Standard_Integer anElemUpper = theFace.ElemUpper();
for (Standard_Integer anElemIter = anElemLower; anElemIter <= anElemUpper && thePSentry.More(); ++anElemIter, thePSentry.Next())
{
const Poly_Triangle aTri = theFace.TriangleOriented (anElemIter);
if (!theWriter.WriteTriangle (Graphic3d_Vec3i (aTri(1), aTri(2), aTri(3)) - Graphic3d_Vec3i (anElemLower)))
{
return false;
}
}
theWriter.SetVertexOffset (theWriter.VertexOffset() + theFace.NbNodes());
return true;
}

View File

@ -0,0 +1,198 @@
// Copyright (c) 2022 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 _RWPly_CafWriter_HeaderFiler
#define _RWPly_CafWriter_HeaderFiler
#include <TColStd_IndexedDataMapOfStringString.hxx>
#include <TColStd_MapOfAsciiString.hxx>
#include <TDF_LabelSequence.hxx>
#include <TopTools_ShapeMapHasher.hxx>
#include <RWMesh_CoordinateSystemConverter.hxx>
#include <XCAFPrs_Style.hxx>
#include <memory>
class Message_ProgressRange;
class RWMesh_FaceIterator;
class TDocStd_Document;
class Message_LazyProgressScope;
class RWPly_PlyWriterContext;
//! PLY writer context from XCAF document.
class RWPly_CafWriter : public Standard_Transient
{
DEFINE_STANDARD_RTTIEXT(RWPly_CafWriter, Standard_Transient)
public:
//! Main constructor.
//! @param[in] theFile path to output PLY file
Standard_EXPORT RWPly_CafWriter (const TCollection_AsciiString& theFile);
//! Destructor.
Standard_EXPORT virtual ~RWPly_CafWriter();
//! Return transformation from OCCT to PLY coordinate system.
const RWMesh_CoordinateSystemConverter& CoordinateSystemConverter() const { return myCSTrsf; }
//! Return transformation from OCCT to PLY coordinate system.
RWMesh_CoordinateSystemConverter& ChangeCoordinateSystemConverter() { return myCSTrsf; }
//! Set transformation from OCCT to PLY coordinate system.
void SetCoordinateSystemConverter (const RWMesh_CoordinateSystemConverter& theConverter) { myCSTrsf = theConverter; }
//! Return default material definition to be used for nodes with only color defined.
const XCAFPrs_Style& DefaultStyle() const { return myDefaultStyle; }
//! Set default material definition to be used for nodes with only color defined.
void SetDefaultStyle (const XCAFPrs_Style& theStyle) { myDefaultStyle = theStyle; }
public:
//! Return TRUE if vertex position should be stored with double floating point precision; FALSE by default.
bool IsDoublePrecision() const { return myIsDoublePrec; }
//! Set if vertex position should be stored with double floating point precision.
void SetDoublePrecision (bool theDoublePrec) { myIsDoublePrec = theDoublePrec; }
//! Return TRUE if normals should be written; TRUE by default.
bool HasNormals() const { return myHasNormals; }
//! Set if normals are defined.
void SetNormals (const bool theHasNormals) { myHasNormals = theHasNormals; }
//! Return TRUE if UV / texture coordinates should be written; FALSE by default.
bool HasTexCoords() const { return myHasTexCoords; }
//! Set if UV / texture coordinates should be written.
void SetTexCoords (const bool theHasTexCoords) { myHasTexCoords = theHasTexCoords; }
//! Return TRUE if point colors should be written; TRUE by default.
bool HasColors() const { return myHasColors; }
//! Set if point colors should be written.
void SetColors (bool theToWrite) { myHasColors = theToWrite; }
//! Return TRUE if part Id should be written as element attribute; TRUE by default.
bool HasPartId() const { return myHasPartId; }
//! Set if part Id should be written as element attribute; FALSE by default.
//! Cannot be combined with HasFaceId().
void SetPartId (bool theSurfId)
{
myHasPartId = theSurfId;
myHasFaceId = myHasFaceId && !myHasPartId;
}
//! Return TRUE if face Id should be written as element attribute; FALSE by default.
bool HasFaceId() const { return myHasFaceId; }
//! Set if face Id should be written as element attribute; FALSE by default.
//! Cannot be combined with HasPartId().
void SetFaceId (bool theSurfId)
{
myHasFaceId = theSurfId;
myHasPartId = myHasPartId && !myHasFaceId;
}
public:
//! Write PLY file and associated MTL material file.
//! Triangulation data should be precomputed within shapes!
//! @param[in] theDocument input document
//! @param[in] theRootLabels list of root shapes to export
//! @param[in] theLabelFilter optional filter with document nodes to export,
//! with keys defined by XCAFPrs_DocumentExplorer::DefineChildId() and filled recursively
//! (leaves and parent assembly nodes at all levels);
//! when not NULL, all nodes not included into the map will be ignored
//! @param[in] theFileInfo map with file metadata to put into PLY header section
//! @param[in] theProgress optional progress indicator
//! @return FALSE on file writing failure
Standard_EXPORT virtual bool Perform (const Handle(TDocStd_Document)& theDocument,
const TDF_LabelSequence& theRootLabels,
const TColStd_MapOfAsciiString* theLabelFilter,
const TColStd_IndexedDataMapOfStringString& theFileInfo,
const Message_ProgressRange& theProgress);
//! Write PLY file and associated MTL material file.
//! Triangulation data should be precomputed within shapes!
//! @param[in] theDocument input document
//! @param[in] theFileInfo map with file metadata to put into PLY header section
//! @param[in] theProgress optional progress indicator
//! @return FALSE on file writing failure
Standard_EXPORT virtual bool Perform (const Handle(TDocStd_Document)& theDocument,
const TColStd_IndexedDataMapOfStringString& theFileInfo,
const Message_ProgressRange& theProgress);
protected:
//! Return TRUE if face mesh should be skipped (e.g. because it is invalid or empty).
Standard_EXPORT virtual Standard_Boolean toSkipFaceMesh (const RWMesh_FaceIterator& theFaceIter);
//! Collect face triangulation info.
//! @param[in] theFace face to process
//! @param[in,out] theNbNodes overall number of triangulation nodes (should be appended)
//! @param[in,out] theNbElems overall number of triangulation elements (should be appended)
Standard_EXPORT virtual void addFaceInfo (const RWMesh_FaceIterator& theFace,
Standard_Integer& theNbNodes,
Standard_Integer& theNbElems);
//! Write the shape.
//! @param[in] theWriter PLY writer context
//! @param[in] thePSentry progress sentry
//! @param[in] theWriteStep export step, 0 for vertex attributes, 1 for elements
//! @param[in] theLabel document label to process
//! @param[in] theParentTrsf parent node transformation
//! @param[in] theParentStyle parent node style
Standard_EXPORT virtual bool writeShape (RWPly_PlyWriterContext& theWriter,
Message_LazyProgressScope& thePSentry,
const Standard_Integer theWriteStep,
const TDF_Label& theLabel,
const TopLoc_Location& theParentTrsf,
const XCAFPrs_Style& theParentStyle);
//! Write face triangle vertices and attributes.
//! @param[in] theWriter PLY writer context
//! @param[in] thePSentry progress sentry
//! @param[in] theFace current face
//! @return FALSE on writing file error
Standard_EXPORT virtual bool writeNodes (RWPly_PlyWriterContext& theWriter,
Message_LazyProgressScope& thePSentry,
const RWMesh_FaceIterator& theFace);
//! Write face triangles indices.
//! @param[in] theWriter PLY writer context
//! @param[in] thePSentry progress sentry
//! @param[in] theFace current face
//! @return FALSE on writing file error
Standard_EXPORT virtual bool writeIndices (RWPly_PlyWriterContext& theWriter,
Message_LazyProgressScope& thePSentry,
const RWMesh_FaceIterator& theFace);
protected:
TCollection_AsciiString myFile; //!< output PLY file
RWMesh_CoordinateSystemConverter myCSTrsf; //!< transformation from OCCT to PLY coordinate system
XCAFPrs_Style myDefaultStyle; //!< default material definition to be used for nodes with only color defined
Standard_Boolean myIsDoublePrec;
Standard_Boolean myHasNormals;
Standard_Boolean myHasColors;
Standard_Boolean myHasTexCoords;
Standard_Boolean myHasPartId;
Standard_Boolean myHasFaceId;
};
#endif // _RWPly_CafWriter_HeaderFiler

View File

@ -0,0 +1,324 @@
// Copyright (c) 2022 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 <RWPly_PlyWriterContext.hxx>
#include <Message.hxx>
#include <NCollection_IndexedMap.hxx>
#include <OSD_FileSystem.hxx>
// =======================================================================
// function : splitLines
// purpose :
// =======================================================================
static void splitLines (const TCollection_AsciiString& theString,
NCollection_IndexedMap<TCollection_AsciiString>& theLines)
{
if (theString.IsEmpty())
{
return;
}
Standard_Integer aLineFrom = 1;
for (Standard_Integer aCharIter = 1;; ++aCharIter)
{
const char aChar = theString.Value (aCharIter);
if (aChar != '\r'
&& aChar != '\n'
&& aCharIter != theString.Length())
{
continue;
}
if (aLineFrom != aCharIter)
{
TCollection_AsciiString aLine = theString.SubString (aLineFrom, aCharIter);
aLine.RightAdjust();
theLines.Add (aLine);
}
if (aCharIter == theString.Length())
{
break;
}
else if (aChar == '\r'
&& theString.Value (aCharIter + 1) == '\n')
{
// CRLF
++aCharIter;
}
aLineFrom = aCharIter + 1;
}
}
// ================================================================
// Function : RWPly_PlyWriterContext
// Purpose :
// ================================================================
RWPly_PlyWriterContext::RWPly_PlyWriterContext()
: myNbHeaderVerts (0),
myNbHeaderElems (0),
myNbVerts (0),
myNbElems (0),
mySurfId (0),
myVertOffset (0),
myIsDoublePrec (false),
myHasNormals (false),
myHasColors (false),
myHasTexCoords (false),
myHasSurfId (false)
{
//
}
// ================================================================
// Function : ~RWPly_PlyWriterContext
// Purpose :
// ================================================================
RWPly_PlyWriterContext::~RWPly_PlyWriterContext()
{
Close();
}
// ================================================================
// Function : Open
// Purpose :
// ================================================================
bool RWPly_PlyWriterContext::Open (const TCollection_AsciiString& theName,
const std::shared_ptr<std::ostream>& theStream)
{
myName = theName;
myNbHeaderVerts = myNbHeaderElems = 0;
myNbVerts = myNbElems = 0;
if (theStream.get() != nullptr)
{
myStream = theStream;
return true;
}
const Handle(OSD_FileSystem)& aFileSystem = OSD_FileSystem::DefaultFileSystem();
myStream = aFileSystem->OpenOStream (theName, std::ios::out | std::ios::binary);
if (myStream.get() == NULL || !myStream->good())
{
myStream.reset();
Message::SendFail() << "Error: file cannot be created\n" << theName;
return false;
}
return true;
}
// ================================================================
// Function : Close
// Purpose :
// ================================================================
bool RWPly_PlyWriterContext::Close (bool theIsAborted)
{
if (myStream.get() == nullptr)
{
return false;
}
myStream->flush();
bool aResult = myStream->good();
if (!aResult)
{
Message::SendFail() << "Error: file cannot be written\n" << myName;
}
else if (!theIsAborted)
{
if (myNbVerts != myNbHeaderVerts)
{
Message::SendFail() << "Error: written less number of vertices (" << myNbVerts << ") than specified in PLY header (" << myNbHeaderVerts << ")";
}
else if (myNbElems != myNbHeaderElems)
{
Message::SendFail() << "Error: written less number of elements (" << myNbElems << ") than specified in PLY header (" << myNbHeaderElems << ")";
}
}
myStream.reset();
return aResult;
}
// ================================================================
// Function : WriteHeader
// Purpose :
// ================================================================
bool RWPly_PlyWriterContext::WriteHeader (const Standard_Integer theNbNodes,
const Standard_Integer theNbElems,
const TColStd_IndexedDataMapOfStringString& theFileInfo)
{
if (myStream.get() == nullptr)
{
return false;
}
myNbHeaderVerts = theNbNodes;
myNbHeaderElems = theNbElems;
*myStream << "ply\n"
"format ascii 1.0\n"
"comment Exported by Open CASCADE Technology [dev.opencascade.org]\n";
for (TColStd_IndexedDataMapOfStringString::Iterator aKeyValueIter (theFileInfo); aKeyValueIter.More(); aKeyValueIter.Next())
{
NCollection_IndexedMap<TCollection_AsciiString> aKeyLines, aValLines;
splitLines (aKeyValueIter.Key(), aKeyLines);
splitLines (aKeyValueIter.Value(), aValLines);
for (Standard_Integer aLineIter = 1; aLineIter <= aKeyLines.Extent(); ++aLineIter)
{
const TCollection_AsciiString& aLine = aKeyLines.FindKey (aLineIter);
*myStream << (aLineIter > 1 ? "\n" : "") << "comment " << aLine;
}
*myStream << (!aKeyLines.IsEmpty() ? ":" : "comment ");
for (Standard_Integer aLineIter = 1; aLineIter <= aValLines.Extent(); ++aLineIter)
{
const TCollection_AsciiString& aLine = aValLines.FindKey (aLineIter);
*myStream << (aLineIter > 1 ? "\n" : "") << "comment " << aLine;
}
*myStream << "\n";
}
*myStream << "element vertex " << theNbNodes<< "\n";
if (myIsDoublePrec)
{
*myStream << "property double x\n"
"property double y\n"
"property double z\n";
}
else
{
*myStream << "property float x\n"
"property float y\n"
"property float z\n";
}
if (myHasNormals)
{
*myStream << "property float nx\n"
"property float ny\n"
"property float nz\n";
}
if (myHasTexCoords)
{
*myStream << "property float s\n"
"property float t\n";
}
if (myHasColors)
{
*myStream << "property uchar red\n"
"property uchar green\n"
"property uchar blue\n";
}
if (theNbElems > 0)
{
*myStream << "element face " << theNbElems << "\n"
"property list uchar uint vertex_indices\n";
if (myHasSurfId)
{
*myStream << "property uint SurfaceID\n";
}
}
*myStream << "end_header\n";
return myStream->good();
}
// ================================================================
// Function : WriteVertex
// Purpose :
// ================================================================
bool RWPly_PlyWriterContext::WriteVertex (const gp_Pnt& thePoint,
const Graphic3d_Vec3& theNorm,
const Graphic3d_Vec2& theUV,
const Graphic3d_Vec4ub& theColor)
{
if (myStream.get() == nullptr)
{
return false;
}
if (myIsDoublePrec)
{
*myStream << (double )thePoint.X() << " " << (double )thePoint.Y() << " " << (double )thePoint.Z();
}
else
{
*myStream << (float )thePoint.X() << " " << (float )thePoint.Y() << " " << (float )thePoint.Z();
}
if (myHasNormals)
{
*myStream << " " << (float )theNorm.x() << " " << (float )theNorm.y() << " " << (float )theNorm.z();
}
if (myHasTexCoords)
{
*myStream << " " << (float )theUV.x() << " " << (float )theUV.y();
}
if (myHasColors)
{
*myStream << " " << (int )theColor.r() << " " << (int )theColor.g() << " " << (int )theColor.b();
}
*myStream << "\n";
if (++myNbVerts > myNbHeaderVerts)
{
throw Standard_OutOfRange ("RWPly_PlyWriterContext::WriteVertex() - number of vertices is greater than defined");
}
return myStream->good();
}
// ================================================================
// Function : WriteTriangle
// Purpose :
// ================================================================
bool RWPly_PlyWriterContext::WriteTriangle (const Graphic3d_Vec3i& theTri)
{
if (myStream.get() == nullptr)
{
return false;
}
const Graphic3d_Vec3i aTri = Graphic3d_Vec3i(myVertOffset) + theTri;
*myStream << "3 " << aTri[0] << " " << aTri[1] << " " << aTri[2];
if (myHasSurfId)
{
*myStream << " " << mySurfId;
}
*myStream << "\n";
if (++myNbElems > myNbHeaderElems)
{
throw Standard_OutOfRange ("RWPly_PlyWriterContext::WriteTriangle() - number of elements is greater than defined");
}
return myStream->good();
}
// ================================================================
// Function : WriteQuad
// Purpose :
// ================================================================
bool RWPly_PlyWriterContext::WriteQuad (const Graphic3d_Vec4i& theQuad)
{
if (myStream.get() == nullptr)
{
return false;
}
const Graphic3d_Vec4i aQuad = Graphic3d_Vec4i(myVertOffset) + theQuad;
*myStream << "4 " << aQuad[0] << " " << aQuad[1] << " " << aQuad[2] << " " << aQuad[3];
if (myHasSurfId)
{
*myStream << " " << mySurfId;
}
*myStream << "\n";
if (++myNbElems > myNbHeaderElems)
{
throw Standard_OutOfRange ("RWPly_PlyWriterContext::WriteQuad() - number of elements is greater than defined");
}
return myStream->good();
}

View File

@ -0,0 +1,142 @@
// Copyright (c) 2022 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 _RWPly_PlyWriterContext_HeaderFiler
#define _RWPly_PlyWriterContext_HeaderFiler
#include <Graphic3d_Vec.hxx>
#include <gp_Pnt.hxx>
#include <TCollection_AsciiString.hxx>
#include <TColStd_IndexedDataMapOfStringString.hxx>
#include <memory>
//! Auxiliary low-level tool writing PLY file.
class RWPly_PlyWriterContext
{
public:
//! Empty constructor.
Standard_EXPORT RWPly_PlyWriterContext();
//! Destructor, will emit error message if file was not closed.
Standard_EXPORT ~RWPly_PlyWriterContext();
public: //! @name vertex attributes parameters
//! Return TRUE if vertex position should be stored with double floating point precision; FALSE by default.
bool IsDoublePrecision() const { return myIsDoublePrec; }
//! Set if vertex position should be stored with double floating point precision.
void SetDoublePrecision (bool theDoublePrec) { myIsDoublePrec = theDoublePrec; }
//! Return TRUE if normals should be written as vertex attribute; FALSE by default.
bool HasNormals() const { return myHasNormals; }
//! Set if normals should be written.
void SetNormals (const bool theHasNormals) { myHasNormals = theHasNormals; }
//! Return TRUE if UV / texture coordinates should be written as vertex attribute; FALSE by default.
bool HasTexCoords() const { return myHasTexCoords; }
//! Set if UV / texture coordinates should be written.
void SetTexCoords (const bool theHasTexCoords) { myHasTexCoords = theHasTexCoords; }
//! Return TRUE if point colors should be written as vertex attribute; FALSE by default.
bool HasColors() const { return myHasColors; }
//! Set if point colors should be written.
void SetColors (bool theToWrite) { myHasColors = theToWrite; }
public: //! @name element attributes parameters
//! Return TRUE if surface Id should be written as element attribute; FALSE by default.
bool HasSurfaceId() const { return myHasSurfId; }
//! Set if surface Id should be written as element attribute; FALSE by default.
void SetSurfaceId (bool theSurfId) { myHasSurfId = theSurfId; }
public: //! @name writing into file
//! Return TRUE if file has been opened.
bool IsOpened() const { return myStream.get() != nullptr; }
//! Open file for writing.
Standard_EXPORT bool Open (const TCollection_AsciiString& theName,
const std::shared_ptr<std::ostream>& theStream = std::shared_ptr<std::ostream>());
//! Write the header.
//! @param[in] theNbNodes number of vertex nodes
//! @param[in] theNbElems number of mesh elements
//! @param[in] theFileInfo optional comments
Standard_EXPORT bool WriteHeader (const Standard_Integer theNbNodes,
const Standard_Integer theNbElems,
const TColStd_IndexedDataMapOfStringString& theFileInfo);
//! Write single point with all attributes.
//! @param[in] thePoint 3D point coordinates
//! @param[in] theNorm surface normal direction at the point
//! @param[in] theUV surface/texture UV coordinates
//! @param[in] theColor RGB color values
Standard_EXPORT bool WriteVertex (const gp_Pnt& thePoint,
const Graphic3d_Vec3& theNorm,
const Graphic3d_Vec2& theUV,
const Graphic3d_Vec4ub& theColor);
//! Return number of written vertices.
Standard_Integer NbWrittenVertices() const { return myNbVerts; }
//! Return vertex offset to be applied to element indices; 0 by default.
Standard_Integer VertexOffset() const { return myVertOffset; }
//! Set vertex offset to be applied to element indices.
void SetVertexOffset (Standard_Integer theOffset) { myVertOffset = theOffset; }
//! Return surface id to write with element; 0 by default.
Standard_Integer SurfaceId() const { return mySurfId; }
//! Set surface id to write with element.
void SetSurfaceId (Standard_Integer theSurfId) { mySurfId = theSurfId; }
//! Writing a triangle.
Standard_EXPORT bool WriteTriangle (const Graphic3d_Vec3i& theTri);
//! Writing a quad.
Standard_EXPORT bool WriteQuad (const Graphic3d_Vec4i& theQuad);
//! Return number of written elements.
Standard_Integer NbWrittenElements() const { return myNbElems; }
//! Correctly close the file.
//! @return FALSE in case of writing error
Standard_EXPORT bool Close (bool theIsAborted = false);
private:
std::shared_ptr<std::ostream> myStream;
TCollection_AsciiString myName;
Standard_Integer myNbHeaderVerts;
Standard_Integer myNbHeaderElems;
Standard_Integer myNbVerts;
Standard_Integer myNbElems;
Standard_Integer mySurfId;
Standard_Integer myVertOffset;
bool myIsDoublePrec;
bool myHasNormals;
bool myHasColors;
bool myHasTexCoords;
bool myHasSurfId;
};
#endif // _RWPly_PlyWriterContext_HeaderFiler

View File

@ -18,24 +18,11 @@
#include <BRepBndLib.hxx>
#include <BRepMesh_DiscretFactory.hxx>
#include <BRepMesh_DiscretRoot.hxx>
#include <BRepTools.hxx>
#include <BRep_Tool.hxx>
#include <GeomAbs_SurfaceType.hxx>
#include <GeomLib.hxx>
#include <gp_XYZ.hxx>
#include <Poly.hxx>
#include <Poly_Connect.hxx>
#include <Poly_Triangulation.hxx>
#include <Precision.hxx>
#include <Prs3d.hxx>
#include <Prs3d_Drawer.hxx>
#include <TColgp_Array1OfPnt.hxx>
#include <TColgp_Array1OfPnt2d.hxx>
#include <TopAbs_Orientation.hxx>
#include <TopLoc_Location.hxx>
#include <TShort_HArray1OfShortReal.hxx>
#include <TShort_Array1OfShortReal.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Face.hxx>
@ -132,66 +119,6 @@ Standard_Boolean StdPrs_ToolTriangulatedShape::IsClosed (const TopoDS_Shape& the
}
}
//=======================================================================
//function : ComputeNormals
//purpose :
//=======================================================================
void StdPrs_ToolTriangulatedShape::ComputeNormals (const TopoDS_Face& theFace,
const Handle(Poly_Triangulation)& theTris,
Poly_Connect& thePolyConnect)
{
if (theTris.IsNull()
|| theTris->HasNormals())
{
return;
}
// take in face the surface location
const TopoDS_Face aZeroFace = TopoDS::Face (theFace.Located (TopLoc_Location()));
Handle(Geom_Surface) aSurf = BRep_Tool::Surface (aZeroFace);
if (!theTris->HasUVNodes() || aSurf.IsNull())
{
// compute normals by averaging triangulation normals sharing the same vertex
Poly::ComputeNormals (theTris);
return;
}
const Standard_Real aTol = Precision::Confusion();
Standard_Integer aTri[3];
gp_Dir aNorm;
theTris->AddNormals();
for (Standard_Integer aNodeIter = 1; aNodeIter <= theTris->NbNodes(); ++aNodeIter)
{
// try to retrieve normal from real surface first, when UV coordinates are available
if (GeomLib::NormEstim (aSurf, theTris->UVNode (aNodeIter), aTol, aNorm) > 1)
{
if (thePolyConnect.Triangulation() != theTris)
{
thePolyConnect.Load (theTris);
}
// compute flat normals
gp_XYZ eqPlan (0.0, 0.0, 0.0);
for (thePolyConnect.Initialize (aNodeIter); thePolyConnect.More(); thePolyConnect.Next())
{
theTris->Triangle (thePolyConnect.Value()).Get (aTri[0], aTri[1], aTri[2]);
const gp_XYZ v1 (theTris->Node (aTri[1]).Coord() - theTris->Node (aTri[0]).Coord());
const gp_XYZ v2 (theTris->Node (aTri[2]).Coord() - theTris->Node (aTri[1]).Coord());
const gp_XYZ vv = v1 ^ v2;
const Standard_Real aMod = vv.Modulus();
if (aMod >= aTol)
{
eqPlan += vv / aMod;
}
}
const Standard_Real aModMax = eqPlan.Modulus();
aNorm = (aModMax > aTol) ? gp_Dir (eqPlan) : gp::DZ();
}
theTris->SetNormal (aNodeIter, aNorm);
}
}
//=======================================================================
//function : Normal
//purpose :

View File

@ -14,19 +14,13 @@
#ifndef _StdPrs_ToolTriangulatedShape_HeaderFile
#define _StdPrs_ToolTriangulatedShape_HeaderFile
#include <Poly_Connect.hxx>
#include <Poly_Triangulation.hxx>
#include <Prs3d_Drawer.hxx>
#include <Standard.hxx>
#include <Standard_Macro.hxx>
#include <BRepLib_ToolTriangulatedShape.hxx>
#include <TColgp_Array1OfDir.hxx>
class TopoDS_Face;
class TopoDS_Shape;
class Prs3d_Drawer;
class Poly_Triangulation;
class StdPrs_ToolTriangulatedShape
class StdPrs_ToolTriangulatedShape: public BRepLib_ToolTriangulatedShape
{
public:
@ -38,26 +32,6 @@ public:
//! @return true if shape is closed manifold Solid or compound of such Solids. <br>
Standard_EXPORT static Standard_Boolean IsClosed (const TopoDS_Shape& theShape);
//! Computes nodal normals for Poly_Triangulation structure using UV coordinates and surface.
//! Does nothing if triangulation already defines normals.
//! @param theFace [in] the face
//! @param theTris [in] the definition of a face triangulation
static void ComputeNormals (const TopoDS_Face& theFace,
const Handle(Poly_Triangulation)& theTris)
{
Poly_Connect aPolyConnect;
ComputeNormals (theFace, theTris, aPolyConnect);
}
//! Computes nodal normals for Poly_Triangulation structure using UV coordinates and surface.
//! Does nothing if triangulation already defines normals.
//! @param theFace [in] the face
//! @param theTris [in] the definition of a face triangulation
//! @param thePolyConnect [in,out] optional, initialized tool for exploring triangulation
Standard_EXPORT static void ComputeNormals (const TopoDS_Face& theFace,
const Handle(Poly_Triangulation)& theTris,
Poly_Connect& thePolyConnect);
//! Evaluate normals for a triangle of a face.
//! @param[in] theFace the face.
//! @param[in] thePolyConnect the definition of a face triangulation.

View File

@ -1,3 +1,4 @@
RWGltf
RWMesh
RWObj
RWPly

View File

@ -40,6 +40,7 @@
#include <AIS_Shape.hxx>
#include <AIS_DisplayMode.hxx>
#include <AIS_PointCloud.hxx>
#include <BRepLib_PointCloudShape.hxx>
#include <TColStd_MapOfInteger.hxx>
#include <ViewerTest_AutoUpdater.hxx>
#include <ViewerTest_DoubleMapOfInteractiveAndName.hxx>
@ -6250,6 +6251,12 @@ static Standard_Integer VPointCloud (Draw_Interpretor& theDI,
Standard_Integer theArgNum,
const char** theArgs)
{
if (theArgNum < 2)
{
Message::SendFail ("Syntax error: wrong number of arguments");
return 1;
}
Handle(AIS_InteractiveContext) anAISContext = ViewerTest::GetAISContext();
if (anAISContext.IsNull())
{
@ -6257,83 +6264,114 @@ static Standard_Integer VPointCloud (Draw_Interpretor& theDI,
return 1;
}
// command to execute
enum Command
{
CloudForShape, // generate point cloud for shape
CloudSphere, // generate point cloud for generic sphere
Unknow
};
TCollection_AsciiString aName;
TopoDS_Shape aShape;
// count number of non-optional command arguments
Command aCmd = Unknow;
Standard_Integer aCmdArgs = 0;
for (Standard_Integer anArgIter = 1; anArgIter < theArgNum; ++anArgIter)
{
Standard_CString anArg = theArgs[anArgIter];
TCollection_AsciiString aFlag (anArg);
aFlag.LowerCase();
if (aFlag.IsRealValue (Standard_True) || aFlag.Search ("-") != 1)
{
aCmdArgs++;
}
}
switch (aCmdArgs)
{
case 2 : aCmd = CloudForShape; break;
case 7 : aCmd = CloudSphere; break;
default :
Message::SendFail ("Syntax error: wrong number of arguments! See usage:");
theDI.PrintHelp (theArgs[0]);
return 1;
}
TCollection_AsciiString aDistribution;
gp_Pnt aDistCenter;
Standard_Real aDistRadius = 0.0;
Standard_Integer aDistNbPoints = 0;
// parse options
Standard_Boolean toRandColors = Standard_False;
Standard_Boolean hasNormals = Standard_True;
Standard_Boolean isSetArgNorm = Standard_False;
Standard_Boolean hasUV = Standard_False;
bool toRandColors = false;
bool hasNormals = true, hasUV = false;
bool isDensityPoints = false;
Standard_Real aDensity = 0.0, aDist = 0.0;
Standard_Real aTol = Precision::Confusion();
for (Standard_Integer anArgIter = 1; anArgIter < theArgNum; ++anArgIter)
{
Standard_CString anArg = theArgs[anArgIter];
TCollection_AsciiString aFlag (anArg);
TCollection_AsciiString aFlag (theArgs[anArgIter]);
aFlag.LowerCase();
if (aFlag == "-randcolors"
|| aFlag == "-randcolor")
{
if (isSetArgNorm && hasNormals)
{
Message::SendFail ("Syntax error: normals can not be enabled with colors at the same time");
return 1;
}
toRandColors = Standard_True;
hasNormals = Standard_False;
toRandColors = Draw::ParseOnOffIterator (theArgNum, theArgs, anArgIter);
}
else if (aFlag == "-normals"
|| aFlag == "-normal")
{
if (toRandColors)
{
Message::SendFail ("Syntax error: normals can not be enabled with colors at the same time");
return 1;
}
isSetArgNorm = Standard_True;
hasNormals = Standard_True;
hasNormals = Draw::ParseOnOffIterator (theArgNum, theArgs, anArgIter);
}
else if (aFlag == "-nonormals"
|| aFlag == "-nonormal")
{
isSetArgNorm = Standard_True;
hasNormals = Standard_False;
hasNormals = !Draw::ParseOnOffIterator (theArgNum, theArgs, anArgIter);
}
else if (aFlag == "-uv"
|| aFlag == "-texels")
{
hasUV = Standard_True;
hasUV = Draw::ParseOnOffIterator (theArgNum, theArgs, anArgIter);
}
else if ((aFlag == "-dist"
|| aFlag == "-distance")
&& anArgIter + 1 < theArgNum
&& Draw::ParseReal (theArgs[anArgIter + 1], aDist))
{
++anArgIter;
if (aDist < 0.0)
{
theDI << "Syntax error: -distance value should be >= 0.0";
return 1;
}
aDist = Max (aDist, Precision::Confusion());
}
else if ((aFlag == "-dens"
|| aFlag == "-density")
&& anArgIter + 1 < theArgNum
&& Draw::ParseReal (theArgs[anArgIter + 1], aDensity))
{
++anArgIter;
isDensityPoints = Standard_True;
if (aDensity <= 0.0)
{
theDI << "Syntax error: -density value should be > 0.0";
return 1;
}
}
else if ((aFlag == "-tol"
|| aFlag == "-tolerance")
&& anArgIter + 1 < theArgNum
&& Draw::ParseReal (theArgs[anArgIter + 1], aTol))
{
++anArgIter;
if (aTol < Precision::Confusion())
{
theDI << "Syntax error: -tol value should be >= " << Precision::Confusion();
return 1;
}
}
else if ((aFlag == "-surface"
|| aFlag == "-volume")
&& anArgIter + 5 < theArgNum)
{
aDistribution = aFlag;
aDistCenter.SetCoord (Draw::Atof (theArgs[anArgIter + 1]),
Draw::Atof (theArgs[anArgIter + 2]),
Draw::Atof (theArgs[anArgIter + 3]));
aDistRadius = Draw::Atof (theArgs[anArgIter + 4]);
aDistNbPoints = Draw::Atoi (theArgs[anArgIter + 5]);
anArgIter += 5;
}
else if (aName.IsEmpty())
{
aName = theArgs[anArgIter];
}
else if (aShape.IsNull())
{
aShape = DBRep::Get (theArgs[anArgIter]);
if (aShape.IsNull())
{
theDI << "Syntax error: invalid shape '" << theArgs[anArgIter] << "'";
return 1;
}
}
else
{
theDI << "Syntax error at '" << theArgs[anArgIter] << "'";
return 1;
}
}
Standard_CString aName = theArgs[1];
Graphic3d_ArrayFlags aFlags = Graphic3d_ArrayFlags_None;
if (hasNormals)
{
@ -6350,125 +6388,80 @@ static Standard_Integer VPointCloud (Draw_Interpretor& theDI,
// generate arbitrary set of points
Handle(Graphic3d_ArrayOfPoints) anArrayPoints;
if (aCmd == CloudForShape)
if (!aShape.IsNull())
{
Standard_CString aShapeName = theArgs[2];
TopoDS_Shape aShape = DBRep::Get (aShapeName);
if (aShape.IsNull())
class PointCloudPntFiller : public BRepLib_PointCloudShape
{
Message::SendFail() << "Error: no shape with name '" << aShapeName << "' found";
return 1;
}
public:
PointCloudPntFiller (Standard_Real theTol) : BRepLib_PointCloudShape (TopoDS_Shape(), theTol) {}
void SetPointArray (const Handle(Graphic3d_ArrayOfPoints)& thePoints) { myPoints = thePoints; }
// calculate number of points
TopLoc_Location aLocation;
Standard_Integer aNbPoints = 0;
for (TopExp_Explorer aFaceIt (aShape, TopAbs_FACE); aFaceIt.More(); aFaceIt.Next())
{
const TopoDS_Face& aFace = TopoDS::Face (aFaceIt.Current());
Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation (aFace, aLocation);
if (!aTriangulation.IsNull())
protected:
virtual void addPoint (const gp_Pnt& thePoint,
const gp_Vec& theNorm,
const gp_Pnt2d& theUV,
const TopoDS_Shape& ) Standard_OVERRIDE
{
aNbPoints += aTriangulation->NbNodes();
}
}
if (aNbPoints < 3)
{
Message::SendFail ("Error: shape should be triangulated");
return 1;
}
anArrayPoints = new Graphic3d_ArrayOfPoints (aNbPoints, aFlags);
for (TopExp_Explorer aFaceIt (aShape, TopAbs_FACE); aFaceIt.More(); aFaceIt.Next())
{
const TopoDS_Face& aFace = TopoDS::Face (aFaceIt.Current());
Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation (aFace, aLocation);
if (aTriangulation.IsNull())
{
continue;
}
const gp_Trsf& aTrsf = aLocation.Transformation();
// extract normals from nodes
TColgp_Array1OfDir aNormals (1, hasNormals ? aTriangulation->NbNodes() : 1);
if (hasNormals)
{
Poly_Connect aPolyConnect (aTriangulation);
StdPrs_ToolTriangulatedShape::Normal (aFace, aPolyConnect, aNormals);
}
for (Standard_Integer aNodeIter = 1; aNodeIter <= aTriangulation->NbNodes(); ++aNodeIter)
{
gp_Pnt aPoint = aTriangulation->Node (aNodeIter);
if (!aLocation.IsIdentity())
const Standard_Integer aPntIndex = myPoints->AddVertex (thePoint, theUV);
if (theNorm.SquareMagnitude() > gp::Resolution())
{
aPoint.Transform (aTrsf);
if (hasNormals)
{
aNormals (aNodeIter).Transform (aTrsf);
}
myPoints->SetVertexNormal (aPntIndex, theNorm);
}
// add vertex into array of points
const Standard_Integer anIndexOfPoint = anArrayPoints->AddVertex (aPoint);
if (toRandColors)
if (myPoints->HasVertexColors())
{
Quantity_Color aColor (360.0 * Standard_Real(anIndexOfPoint) / Standard_Real(aNbPoints),
Quantity_Color aColor (360.0 * Standard_Real(aPntIndex) / Standard_Real(myPoints->VertexNumberAllocated()),
1.0, 0.5, Quantity_TOC_HLS);
anArrayPoints->SetVertexColor (anIndexOfPoint, aColor);
}
if (hasNormals)
{
anArrayPoints->SetVertexNormal (anIndexOfPoint, aNormals (aNodeIter));
}
if (hasUV
&& aTriangulation->HasUVNodes())
{
anArrayPoints->SetVertexTexel (anIndexOfPoint, aTriangulation->UVNode (aNodeIter));
myPoints->SetVertexColor (aPntIndex, aColor);
}
}
private:
Handle(Graphic3d_ArrayOfPoints) myPoints;
};
PointCloudPntFiller aPoitCloudTool (aTol);
aPoitCloudTool.SetShape (aShape);
aPoitCloudTool.SetDistance (aDist);
Standard_Integer aNbPoints = isDensityPoints
? aPoitCloudTool.NbPointsByDensity (aDensity)
: aPoitCloudTool.NbPointsByTriangulation();
theDI << "Number of the generated points : " << aNbPoints << "\n";
anArrayPoints = new Graphic3d_ArrayOfPoints (aNbPoints, aFlags);
aPoitCloudTool.SetPointArray (anArrayPoints);
Standard_Boolean isDone = isDensityPoints
? aPoitCloudTool.GeneratePointsByDensity (aDensity)
: aPoitCloudTool.GeneratePointsByTriangulation();
if (!isDone)
{
Message::SendFail() << "Error: Point cloud was not generated";
return 1;
}
}
else if (aCmd == CloudSphere)
else if (!aDistribution.IsEmpty())
{
Standard_Real aCenterX = Draw::Atof (theArgs[2]);
Standard_Real aCenterY = Draw::Atof (theArgs[3]);
Standard_Real aCenterZ = Draw::Atof (theArgs[4]);
Standard_Real aRadius = Draw::Atof (theArgs[5]);
Standard_Integer aNbPoints = Draw::Atoi (theArgs[6]);
const bool isSurface = aDistribution == "-surface";
TCollection_AsciiString aDistribution = TCollection_AsciiString(theArgs[7]);
aDistribution.LowerCase();
if ( aDistribution != "surface" && aDistribution != "volume" )
anArrayPoints = new Graphic3d_ArrayOfPoints (aDistNbPoints, aFlags);
std::mt19937 aRandomGenerator(0);
std::uniform_real_distribution<> anAlphaDistrib(0.0, 2.0 * M_PI);
std::uniform_real_distribution<> aBetaDistrib (0.0, 2.0 * M_PI);
std::uniform_real_distribution<> aRadiusDistrib(0.0, aDistRadius);
for (Standard_Integer aPntIt = 0; aPntIt < aDistNbPoints; ++aPntIt)
{
Message::SendFail ("Syntax error: wrong arguments. See usage:");
theDI.PrintHelp (theArgs[0]);
return 1;
}
Standard_Boolean isSurface = aDistribution == "surface";
gp_Pnt aCenter(aCenterX, aCenterY, aCenterZ);
anArrayPoints = new Graphic3d_ArrayOfPoints (aNbPoints, aFlags);
for (Standard_Integer aPntIt = 0; aPntIt < aNbPoints; ++aPntIt)
{
Standard_Real anAlpha = (Standard_Real (rand() % 2000) / 1000.0) * M_PI;
Standard_Real aBeta = (Standard_Real (rand() % 2000) / 1000.0) * M_PI;
Standard_Real aDistance = isSurface ?
aRadius : (Standard_Real (rand() % aNbPoints) / aNbPoints) * aRadius;
Standard_Real anAlpha = anAlphaDistrib(aRandomGenerator);
Standard_Real aBeta = aBetaDistrib (aRandomGenerator);
Standard_Real aDistance = isSurface ? aDistRadius : aRadiusDistrib (aRandomGenerator);
gp_Dir aDir (Cos (anAlpha) * Sin (aBeta),
Sin (anAlpha),
Cos (anAlpha) * Cos (aBeta));
gp_Pnt aPoint = aCenter.Translated (aDir.XYZ() * aDistance);
gp_Pnt aPoint = aDistCenter.Translated (aDir.XYZ() * aDistance);
const Standard_Integer anIndexOfPoint = anArrayPoints->AddVertex (aPoint);
if (toRandColors)
{
Quantity_Color aColor (360.0 * Standard_Real (anIndexOfPoint) / Standard_Real (aNbPoints),
Quantity_Color aColor (360.0 * Standard_Real (anIndexOfPoint) / Standard_Real (aDistNbPoints),
1.0, 0.5, Quantity_TOC_HLS);
anArrayPoints->SetVertexColor (anIndexOfPoint, aColor);
}
@ -6484,11 +6477,16 @@ static Standard_Integer VPointCloud (Draw_Interpretor& theDI,
}
}
}
else
{
Message::SendFail ("Error: wrong number of arguments");
return 1;
}
// set array of points in point cloud object
Handle(AIS_PointCloud) aPointCloud = new AIS_PointCloud();
aPointCloud->SetPoints (anArrayPoints);
VDisplayAISObject (aName, aPointCloud);
ViewerTest::Display (aName, aPointCloud);
return 0;
}
@ -7149,18 +7147,23 @@ Prints the default vertex draw mode without -set parameter.
)" /* [vvertexmode] */);
addCmd ("vpointcloud", VPointCloud, /* [vpointcloud] */ R"(
vpointcloud name shape [-randColor] [-normals] [-noNormals] [-uv]
vpointcloud name shape [-randColor {0|1}]=0 [-normals {0|1}]=1 [-uv {0|1}]=0
[-distance Value]=0.0 [-density Value] [-tolerance Value]
Create an interactive object for arbitrary set of points from triangulated shape.
vpointcloud name x y z r npts {surface|volume}
... [-randColor] [-normals] [-noNormals] [-uv]
vpointcloud name {-surface|-volume} x y z r npts
[-randColor] [-normals] [-uv]
Create arbitrary set of points (npts) randomly distributed
on spheric surface or within spheric volume (x y z r).
Additional options:
-randColor - generate random color per point
-normals - generate normal per point (default)
-noNormals - do not generate normal per point
-normals generate or not normal per point
-uv generate UV (texel) coordinates per point
-randColor generate random color per point
-distance distance from shape into the range [0, Value];
-density density of points to generate randomly on surface;
-tolerance cloud generator's tolerance; default value is Precision::Confusion();
)" /* [vpointcloud] */);
addCmd ("vpriority", VPriority, /* [vpriority] */ R"(

View File

@ -19,6 +19,7 @@
#include <Aspect_TypeOfMarker.hxx>
#include <Bnd_Box.hxx>
#include <BRep_Builder.hxx>
#include <BRepLib_PointCloudShape.hxx>
#include <DBRep.hxx>
#include <DDocStd.hxx>
#include <DDocStd_DrawDocument.hxx>
@ -45,10 +46,13 @@
#include <Quantity_NameOfColor.hxx>
#include <RWGltf_CafReader.hxx>
#include <RWGltf_CafWriter.hxx>
#include <RWMesh_FaceIterator.hxx>
#include <RWStl.hxx>
#include <RWObj.hxx>
#include <RWObj_CafReader.hxx>
#include <RWObj_CafWriter.hxx>
#include <RWPly_CafWriter.hxx>
#include <RWPly_PlyWriterContext.hxx>
#include <SelectMgr_SelectionManager.hxx>
#include <Standard_ErrorHandler.hxx>
#include <StdSelect_ViewerSelector3d.hxx>
@ -62,6 +66,7 @@
#include <TDataStd_Name.hxx>
#include <TDocStd_Application.hxx>
#include <TDocStd_Document.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Shape.hxx>
#include <UnitsAPI.hxx>
@ -75,6 +80,7 @@
#include <VrmlData_ShapeConvert.hxx>
#include <XCAFDoc_DocumentTool.hxx>
#include <XCAFDoc_ShapeTool.hxx>
#include <XCAFPrs_DocumentExplorer.hxx>
#include <XSAlgo.hxx>
#include <XSAlgo_AlgoContainer.hxx>
#include <XSDRAW.hxx>
@ -2055,6 +2061,275 @@ static Standard_Integer meshinfo(Draw_Interpretor& di,
return 0;
}
//=======================================================================
//function : writeply
//purpose : write PLY file
//=======================================================================
static Standard_Integer WritePly (Draw_Interpretor& theDI,
Standard_Integer theNbArgs,
const char** theArgVec)
{
Handle(TDocStd_Document) aDoc;
Handle(TDocStd_Application) anApp = DDocStd::GetApplication();
TCollection_AsciiString aShapeName, aFileName;
Standard_Real aDist = 0.0;
Standard_Real aDens = Precision::Infinite();
Standard_Real aTol = Precision::Confusion();
bool hasColors = true, hasNormals = true, hasTexCoords = false, hasPartId = true, hasFaceId = false;
bool isPntSet = false, isDensityPoints = false;
TColStd_IndexedDataMapOfStringString aFileInfo;
for (Standard_Integer anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
{
TCollection_AsciiString anArg (theArgVec[anArgIter]);
anArg.LowerCase();
if (anArg == "-normal")
{
hasNormals = Draw::ParseOnOffIterator (theNbArgs, theArgVec, anArgIter);
}
else if (anArg == "-nonormal")
{
hasNormals = !Draw::ParseOnOffIterator (theNbArgs, theArgVec, anArgIter);
}
else if (anArg == "-color"
|| anArg == "-nocolor"
|| anArg == "-colors"
|| anArg == "-nocolors")
{
hasColors = Draw::ParseOnOffNoIterator (theNbArgs, theArgVec, anArgIter);
}
else if (anArg == "-uv"
|| anArg == "-nouv")
{
hasTexCoords = Draw::ParseOnOffNoIterator (theNbArgs, theArgVec, anArgIter);
}
else if (anArg == "-partid")
{
hasPartId = Draw::ParseOnOffNoIterator (theNbArgs, theArgVec, anArgIter);
hasFaceId = hasFaceId && !hasPartId;
}
else if (anArg == "-surfid"
|| anArg == "-surfaceid"
|| anArg == "-faceid")
{
hasFaceId = Draw::ParseOnOffNoIterator (theNbArgs, theArgVec, anArgIter);
hasPartId = hasPartId && !hasFaceId;
}
else if (anArg == "-pntset"
|| anArg == "-pntcloud"
|| anArg == "-pointset"
|| anArg == "-pointcloud"
|| anArg == "-cloud"
|| anArg == "-points")
{
isPntSet = Draw::ParseOnOffIterator (theNbArgs, theArgVec, anArgIter);
}
else if ((anArg == "-dist"
|| anArg == "-distance")
&& anArgIter + 1 < theNbArgs
&& Draw::ParseReal (theArgVec[anArgIter + 1], aDist))
{
++anArgIter;
isPntSet = true;
if (aDist < 0.0)
{
theDI << "Syntax error: -distance value should be >= 0.0";
return 1;
}
aDist = Max (aDist, Precision::Confusion());
}
else if ((anArg == "-dens"
|| anArg == "-density")
&& anArgIter + 1 < theNbArgs
&& Draw::ParseReal (theArgVec[anArgIter + 1], aDens))
{
++anArgIter;
isDensityPoints = Standard_True;
isPntSet = true;
if (aDens <= 0.0)
{
theDI << "Syntax error: -density value should be > 0.0";
return 1;
}
}
else if ((anArg == "-tol"
|| anArg == "-tolerance")
&& anArgIter + 1 < theNbArgs
&& Draw::ParseReal (theArgVec[anArgIter + 1], aTol))
{
++anArgIter;
isPntSet = true;
if (aTol < Precision::Confusion())
{
theDI << "Syntax error: -tol value should be >= " << Precision::Confusion();
return 1;
}
}
else if (anArg == "-comments"
&& anArgIter + 1 < theNbArgs)
{
aFileInfo.Add ("Comments", theArgVec[++anArgIter]);
}
else if (anArg == "-author"
&& anArgIter + 1 < theNbArgs)
{
aFileInfo.Add ("Author", theArgVec[++anArgIter]);
}
else if (aDoc.IsNull())
{
if (aShapeName.IsEmpty())
{
aShapeName = theArgVec[anArgIter];
}
Standard_CString aNameVar = theArgVec[anArgIter];
DDocStd::GetDocument (aNameVar, aDoc, false);
if (aDoc.IsNull())
{
TopoDS_Shape aShape = DBRep::Get (aNameVar);
if (!aShape.IsNull())
{
anApp->NewDocument (TCollection_ExtendedString ("BinXCAF"), aDoc);
Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool (aDoc->Main());
aShapeTool->AddShape (aShape);
}
}
}
else if (aFileName.IsEmpty())
{
aFileName = theArgVec[anArgIter];
}
else
{
theDI << "Syntax error at '" << theArgVec[anArgIter] << "'";
return 1;
}
}
if (aDoc.IsNull()
&& !aShapeName.IsEmpty())
{
theDI << "Syntax error: '" << aShapeName << "' is not a shape nor document";
return 1;
}
else if (aDoc.IsNull()
|| aFileName.IsEmpty())
{
theDI << "Syntax error: wrong number of arguments";
return 1;
}
TDF_LabelSequence aRootLabels;
Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool (aDoc->Main());
aShapeTool->GetFreeShapes (aRootLabels);
if (aRootLabels.IsEmpty())
{
theDI << "Error: empty document";
return 1;
}
if (isPntSet)
{
class PointCloudPlyWriter : public BRepLib_PointCloudShape, public RWPly_PlyWriterContext
{
public:
PointCloudPlyWriter (Standard_Real theTol)
: BRepLib_PointCloudShape (TopoDS_Shape(), theTol) {}
void AddFaceColor (const TopoDS_Shape& theFace, const Graphic3d_Vec4ub& theColor)
{ myFaceColor.Bind (theFace, theColor); }
protected:
virtual void addPoint (const gp_Pnt& thePoint,
const gp_Vec& theNorm,
const gp_Pnt2d& theUV,
const TopoDS_Shape& theFace)
{
Graphic3d_Vec4ub aColor;
myFaceColor.Find (theFace, aColor);
RWPly_PlyWriterContext::WriteVertex (thePoint,
Graphic3d_Vec3 ((float )theNorm.X(), (float )theNorm.Y(), (float )theNorm.Z()),
Graphic3d_Vec2 ((float )theUV.X(), (float )theUV.Y()),
aColor);
}
private:
NCollection_DataMap<TopoDS_Shape, Graphic3d_Vec4ub> myFaceColor;
};
PointCloudPlyWriter aPlyCtx (aTol);
aPlyCtx.SetNormals (hasNormals);
aPlyCtx.SetColors (hasColors);
aPlyCtx.SetTexCoords (hasTexCoords);
TopoDS_Compound aComp;
BRep_Builder().MakeCompound (aComp);
for (XCAFPrs_DocumentExplorer aDocExplorer (aDoc, aRootLabels, XCAFPrs_DocumentExplorerFlags_OnlyLeafNodes);
aDocExplorer.More(); aDocExplorer.Next())
{
const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current();
for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, aDocNode.Location, true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next())
{
BRep_Builder().Add (aComp, aFaceIter.Face());
Graphic3d_Vec4ub aColorVec (255);
if (aFaceIter.HasFaceColor())
{
Graphic3d_Vec4 aColorF = aFaceIter.FaceColor();
aColorVec.SetValues ((unsigned char )int(aColorF.r() * 255.0f),
(unsigned char )int(aColorF.g() * 255.0f),
(unsigned char )int(aColorF.b() * 255.0f),
(unsigned char )int(aColorF.a() * 255.0f));
}
aPlyCtx.AddFaceColor (aFaceIter.Face(), aColorVec);
}
}
aPlyCtx.SetShape (aComp);
Standard_Integer aNbPoints = isDensityPoints
? aPlyCtx.NbPointsByDensity (aDens)
: aPlyCtx.NbPointsByTriangulation();
if (aNbPoints <= 0)
{
theDI << "Error: unable to generate points";
return 0;
}
if (!aPlyCtx.Open (aFileName)
|| !aPlyCtx.WriteHeader (aNbPoints, 0, TColStd_IndexedDataMapOfStringString()))
{
theDI << "Error: unable to create file '" << aFileName << "'";
return 0;
}
Standard_Boolean isDone = isDensityPoints
? aPlyCtx.GeneratePointsByDensity (aDens)
: aPlyCtx.GeneratePointsByTriangulation();
if (!isDone)
{
theDI << "Error: Point cloud was not generated in file '" << aFileName << "'";
}
else if (!aPlyCtx.Close())
{
theDI << "Error: Point cloud file '" << aFileName << "' was not written";
}
else
{
theDI << aNbPoints;
}
}
else
{
Handle(Draw_ProgressIndicator) aProgress = new Draw_ProgressIndicator (theDI, 1);
RWPly_CafWriter aPlyCtx (aFileName);
aPlyCtx.SetNormals (hasNormals);
aPlyCtx.SetColors (hasColors);
aPlyCtx.SetTexCoords (hasTexCoords);
aPlyCtx.SetPartId (hasPartId);
aPlyCtx.SetFaceId (hasFaceId);
aPlyCtx.Perform (aDoc, aFileInfo, aProgress->Start());
}
return 0;
}
//-----------------------------------------------------------------------------
void XSDRAWSTLVRML::InitCommands (Draw_Interpretor& theCommands)
@ -2160,6 +2435,25 @@ void XSDRAWSTLVRML::InitCommands (Draw_Interpretor& theCommands)
theCommands.Add ("meshdeform", "display deformed mesh", __FILE__, meshdeform, g );
theCommands.Add ("mesh_edge_width", "set width of edges", __FILE__, mesh_edge_width, g );
theCommands.Add ("meshinfo", "displays the number of nodes and triangles", __FILE__, meshinfo, g );
theCommands.Add ("WritePly", R"(
WritePly Doc file [-normals {0|1}]=1 [-colors {0|1}]=1 [-uv {0|1}]=0 [-partId {0|1}]=1 [-faceId {0|1}]=0
[-pointCloud {0|1}]=0 [-distance Value]=0.0 [-density Value] [-tolerance Value]
Write document or triangulated shape into PLY file.
-normals write per-vertex normals
-colors write per-vertex colors
-uv write per-vertex UV coordinates
-partId write per-element part index (alternative to -faceId)
-faceId write per-element face index (alternative to -partId)
Generate point cloud out of the shape and write it into PLY file.
-pointCloud write point cloud instead without triangulation indices
-distance sets distance from shape into the range [0, Value];
-density sets density of points to generate randomly on surface;
-tolerance sets tolerance; default value is Precision::Confusion();
)", __FILE__, WritePly, g);
theCommands.Add ("writeply",
"writeply shape file",
__FILE__, WritePly, g);
}
//==============================================================================

View File

@ -5,3 +5,4 @@
005 gltf_lateload
006 obj_read
007 obj_write
008 ply_write

View File

@ -0,0 +1,14 @@
puts "============"
puts "0029325: Modeling Algorithms - add tool BRepLib_PointCloudShape for generation point cloud for specified shape"
puts "============"
puts ""
pload XDE OCAF MODELING VISUALIZATION
set aNbPntsExpected 32581
set aTmpPly ${imagedir}/${casename}_tmp.ply
lappend occ_tmp_files $aTmpPly
restore [locate_data_file bug29325_EQUERRE.brep] aShape
set aNbPnts [writeply aShape $aTmpPly -pointCloud -dist 0.0 -dens 0.1 -colors 0]
if {$aNbPnts != $aNbPntsExpected} { puts "Error: ($aNbPnts) generated while expected ($aNbPntsExpected)" }

View File

@ -0,0 +1,14 @@
puts "============"
puts "0029325: Modeling Algorithms - add tool BRepLib_PointCloudShape for generation point cloud for specified shape"
puts "============"
puts ""
pload XDE OCAF MODELING VISUALIZATION
set aNbPntsExpected 27890
set aTmpPly ${imagedir}/${casename}_tmp.ply
lappend occ_tmp_files $aTmpPly
restore [locate_data_file bug29325_SANGLE_DE_FIXATION.brep] aShape
set aNbPnts [writeply aShape $aTmpPly -pointCloud -dist 0.0 -dens 0.5 -colors 0]
if {$aNbPnts != $aNbPntsExpected} { puts "Error: ($aNbPnts) generated while expected ($aNbPntsExpected)" }

View File

@ -21,7 +21,7 @@ vrotate 0.2 0.0 0.0
vdump $::imagedir/${::casename}_green.png
# random colors mode
vpointcloud p s -randcolors
vpointcloud p s -randcolors -nonormals
vdump $::imagedir/${::casename}_rand.png
# texture mapping