/*************************************************************************** * Copyright (c) 2008 Jürgen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "FitBSplineCurve.h" #include "FitBSplineSurface.h" #include "Poisson.h" #include "Segmentation.h" #include "SegmentationManual.h" using namespace std; DEF_STD_CMD_A(CmdApproxCurve) CmdApproxCurve::CmdApproxCurve() : Command("Reen_ApproxCurve") { sAppModule = "Reen"; sGroup = QT_TR_NOOP("Reverse Engineering"); sMenuText = QT_TR_NOOP("Approximate B-Spline Curve…"); sToolTipText = QT_TR_NOOP("Approximates a B-spline curve"); sWhatsThis = "Reen_ApproxCurve"; sStatusTip = sToolTipText; } void CmdApproxCurve::activated(int) { App::DocumentObjectT objT; auto obj = Gui::Selection().getObjectsOfType(App::GeoFeature::getClassTypeId()); if (obj.size() != 1 || !(obj.at(0)->isDerivedFrom())) { QMessageBox::warning( Gui::getMainWindow(), qApp->translate("Reen_ApproxSurface", "Wrong selection"), qApp->translate("Reen_ApproxSurface", "Select a point cloud.") ); return; } objT = obj.front(); Gui::Control().showDialog(new ReenGui::TaskFitBSplineCurve(objT)); } bool CmdApproxCurve::isActive() { return (hasActiveDocument() && !Gui::Control().activeDialog()); } DEF_STD_CMD_A(CmdApproxSurface) CmdApproxSurface::CmdApproxSurface() : Command("Reen_ApproxSurface") { sAppModule = "Reen"; sGroup = QT_TR_NOOP("Reverse Engineering"); sMenuText = QT_TR_NOOP("Approximate B-Spline Surface…"); sToolTipText = QT_TR_NOOP("Approximates a B-spline surface"); sWhatsThis = "Reen_ApproxSurface"; sStatusTip = sToolTipText; sPixmap = "actions/FitSurface"; } void CmdApproxSurface::activated(int) { App::DocumentObjectT objT; std::vector obj = Gui::Selection().getObjectsOfType( App::GeoFeature::getClassTypeId() ); if (obj.size() != 1 || !(obj.at(0)->isDerivedFrom() || obj.at(0)->isDerivedFrom())) { QMessageBox::warning( Gui::getMainWindow(), qApp->translate("Reen_ApproxSurface", "Wrong selection"), qApp->translate("Reen_ApproxSurface", "Select a point cloud or mesh.") ); return; } objT = obj.front(); Gui::Control().showDialog(new ReenGui::TaskFitBSplineSurface(objT)); } bool CmdApproxSurface::isActive() { return (hasActiveDocument() && !Gui::Control().activeDialog()); } DEF_STD_CMD_A(CmdApproxPlane) CmdApproxPlane::CmdApproxPlane() : Command("Reen_ApproxPlane") { sAppModule = "Reen"; sGroup = QT_TR_NOOP("Reverse Engineering"); sMenuText = QT_TR_NOOP("Plane"); sToolTipText = QT_TR_NOOP("Approximates a plane"); sWhatsThis = "Reen_ApproxPlane"; sStatusTip = sToolTipText; } void CmdApproxPlane::activated(int) { std::vector obj = Gui::Selection().getObjectsOfType(); for (const auto& it : obj) { std::vector aPoints; std::vector aNormals; std::vector List; it->getPropertyList(List); for (const auto& jt : List) { if (jt->isDerivedFrom()) { const Data::ComplexGeoData* data = static_cast(jt)->getComplexData(); if (data) { data->getPoints(aPoints, aNormals, 0.01f); if (!aPoints.empty()) { break; } } } } if (!aPoints.empty()) { // get a reference normal for the plane fit Base::Vector3f refNormal(0, 0, 0); if (!aNormals.empty()) { refNormal = Base::convertTo(aNormals.front()); } std::vector aData; aData.reserve(aPoints.size()); for (const auto& jt : aPoints) { aData.push_back(Base::toVector(jt)); } MeshCore::PlaneFit fit; fit.AddPoints(aData); float sigma = fit.Fit(); Base::Vector3f base = fit.GetBase(); Base::Vector3f dirU = fit.GetDirU(); Base::Vector3f dirV = fit.GetDirV(); Base::Vector3f norm = fit.GetNormal(); // if the dot product of the reference with the plane normal is negative // a flip must be done if (refNormal * norm < 0) { norm = -norm; dirU = -dirU; } float length, width; fit.Dimension(length, width); // move to the corner point base = base - (0.5f * length * dirU + 0.5f * width * dirV); Base::CoordinateSystem cs; cs.setPosition(Base::convertTo(base)); cs.setAxes(Base::convertTo(norm), Base::convertTo(dirU)); Base::Placement pm = Base::CoordinateSystem().displacement(cs); double q0, q1, q2, q3; pm.getRotation().getValue(q0, q1, q2, q3); Base::Console().log("RMS value for plane fit with %lu points: %.4f\n", aData.size(), sigma); Base::Console().log(" Plane base(%.4f, %.4f, %.4f)\n", base.x, base.y, base.z); Base::Console().log(" Plane normal(%.4f, %.4f, %.4f)\n", norm.x, norm.y, norm.z); std::stringstream str; str << "from FreeCAD import Base" << std::endl; str << "App.ActiveDocument.addObject('Part::Plane','Plane_fit')" << std::endl; str << "App.ActiveDocument.ActiveObject.Length = " << length << std::endl; str << "App.ActiveDocument.ActiveObject.Width = " << width << std::endl; str << "App.ActiveDocument.ActiveObject.Placement = Base.Placement(" << "Base.Vector(" << base.x << "," << base.y << "," << base.z << ")," << "Base.Rotation(" << q0 << "," << q1 << "," << q2 << "," << q3 << "))" << std::endl; openCommand(QT_TRANSLATE_NOOP("Command", "Fit plane")); runCommand(Gui::Command::Doc, str.str().c_str()); commitCommand(); updateActive(); } } } bool CmdApproxPlane::isActive() { if (getSelection().countObjectsOfType() > 0) { return true; } return false; } DEF_STD_CMD_A(CmdApproxCylinder) CmdApproxCylinder::CmdApproxCylinder() : Command("Reen_ApproxCylinder") { sAppModule = "Reen"; sGroup = QT_TR_NOOP("Reverse Engineering"); sMenuText = QT_TR_NOOP("Cylinder"); sToolTipText = QT_TR_NOOP("Approximates a cylinder"); sWhatsThis = "Reen_ApproxCylinder"; sStatusTip = sToolTipText; } void CmdApproxCylinder::activated(int) { std::vector sel = getSelection().getObjectsOfType(); openCommand(QT_TRANSLATE_NOOP("Command", "Fit cylinder")); for (auto it : sel) { const Mesh::MeshObject& mesh = it->Mesh.getValue(); const MeshCore::MeshKernel& kernel = mesh.getKernel(); MeshCore::CylinderFit fit; fit.AddPoints(kernel.GetPoints()); // get normals { std::vector facets(kernel.CountFacets()); std::generate(facets.begin(), facets.end(), Base::iotaGen(0)); std::vector normals = kernel.GetFacetNormals(facets); Base::Vector3f base = fit.GetGravity(); Base::Vector3f axis = fit.GetInitialAxisFromNormals(normals); fit.SetInitialValues(base, axis); } if (fit.Fit() < std::numeric_limits::max()) { Base::Vector3f base, top; fit.GetBounding(base, top); float height = Base::Distance(base, top); Base::Rotation rot; rot.setValue(Base::Vector3d(0, 0, 1), Base::convertTo(fit.GetAxis())); double q0, q1, q2, q3; rot.getValue(q0, q1, q2, q3); std::stringstream str; str << "from FreeCAD import Base" << std::endl; str << "App.ActiveDocument.addObject('Part::Cylinder','Cylinder_fit')" << std::endl; str << "App.ActiveDocument.ActiveObject.Radius = " << fit.GetRadius() << std::endl; str << "App.ActiveDocument.ActiveObject.Height = " << height << std::endl; str << "App.ActiveDocument.ActiveObject.Placement = Base.Placement(" << "Base.Vector(" << base.x << "," << base.y << "," << base.z << ")," << "Base.Rotation(" << q0 << "," << q1 << "," << q2 << "," << q3 << "))" << std::endl; runCommand(Gui::Command::Doc, str.str().c_str()); } } commitCommand(); updateActive(); } bool CmdApproxCylinder::isActive() { if (getSelection().countObjectsOfType() > 0) { return true; } return false; } DEF_STD_CMD_A(CmdApproxSphere) CmdApproxSphere::CmdApproxSphere() : Command("Reen_ApproxSphere") { sAppModule = "Reen"; sGroup = QT_TR_NOOP("Reverse Engineering"); sMenuText = QT_TR_NOOP("Sphere"); sToolTipText = QT_TR_NOOP("Approximates a sphere"); sWhatsThis = "Reen_ApproxSphere"; sStatusTip = sToolTipText; } void CmdApproxSphere::activated(int) { std::vector sel = getSelection().getObjectsOfType(); openCommand(QT_TRANSLATE_NOOP("Command", "Fit sphere")); for (auto it : sel) { const Mesh::MeshObject& mesh = it->Mesh.getValue(); const MeshCore::MeshKernel& kernel = mesh.getKernel(); MeshCore::SphereFit fit; fit.AddPoints(kernel.GetPoints()); if (fit.Fit() < std::numeric_limits::max()) { Base::Vector3f base = fit.GetCenter(); std::stringstream str; str << "from FreeCAD import Base" << std::endl; str << "App.ActiveDocument.addObject('Part::Sphere','Sphere_fit')" << std::endl; str << "App.ActiveDocument.ActiveObject.Radius = " << fit.GetRadius() << std::endl; str << "App.ActiveDocument.ActiveObject.Placement = Base.Placement(" << "Base.Vector(" << base.x << "," << base.y << "," << base.z << ")," << "Base.Rotation(" << 1 << "," << 0 << "," << 0 << "," << 0 << "))" << std::endl; runCommand(Gui::Command::Doc, str.str().c_str()); } } commitCommand(); updateActive(); } bool CmdApproxSphere::isActive() { if (getSelection().countObjectsOfType() > 0) { return true; } return false; } DEF_STD_CMD_A(CmdApproxPolynomial) CmdApproxPolynomial::CmdApproxPolynomial() : Command("Reen_ApproxPolynomial") { sAppModule = "Reen"; sGroup = QT_TR_NOOP("Reverse Engineering"); sMenuText = QT_TR_NOOP("Polynomial Surface"); sToolTipText = QT_TR_NOOP("Approximates a polynomial surface"); sWhatsThis = "Reen_ApproxPolynomial"; sStatusTip = sToolTipText; } void CmdApproxPolynomial::activated(int) { std::vector sel = getSelection().getObjectsOfType(); App::Document* doc = App::GetApplication().getActiveDocument(); openCommand(QT_TRANSLATE_NOOP("Command", "Fit polynomial surface")); for (auto it : sel) { const Mesh::MeshObject& mesh = it->Mesh.getValue(); const MeshCore::MeshKernel& kernel = mesh.getKernel(); MeshCore::SurfaceFit fit; fit.AddPoints(kernel.GetPoints()); if (fit.Fit() < std::numeric_limits::max()) { Base::BoundBox3f bbox = fit.GetBoundings(); std::vector poles = fit.toBezier(bbox.MinX, bbox.MaxX, bbox.MinY, bbox.MaxY); fit.Transform(poles); TColgp_Array2OfPnt grid(1, 3, 1, 3); grid.SetValue(1, 1, Base::convertTo(poles.at(0))); grid.SetValue(2, 1, Base::convertTo(poles.at(1))); grid.SetValue(3, 1, Base::convertTo(poles.at(2))); grid.SetValue(1, 2, Base::convertTo(poles.at(3))); grid.SetValue(2, 2, Base::convertTo(poles.at(4))); grid.SetValue(3, 2, Base::convertTo(poles.at(5))); grid.SetValue(1, 3, Base::convertTo(poles.at(6))); grid.SetValue(2, 3, Base::convertTo(poles.at(7))); grid.SetValue(3, 3, Base::convertTo(poles.at(8))); Handle(Geom_BezierSurface) bezier(new Geom_BezierSurface(grid)); Part::Feature* part = static_cast(doc->addObject("Part::Spline", "Bezier")); part->Shape.setValue(Part::GeomBezierSurface(bezier).toShape()); } } commitCommand(); updateActive(); } bool CmdApproxPolynomial::isActive() { if (getSelection().countObjectsOfType() > 0) { return true; } return false; } DEF_STD_CMD_A(CmdSegmentation) CmdSegmentation::CmdSegmentation() : Command("Reen_Segmentation") { sAppModule = "Reen"; sGroup = QT_TR_NOOP("Reverse Engineering"); sMenuText = QT_TR_NOOP("Mesh Segmentation…"); sToolTipText = QT_TR_NOOP("Creates separate mesh segments based on surface types"); sWhatsThis = "Reen_Segmentation"; sStatusTip = sToolTipText; } void CmdSegmentation::activated(int) { std::vector objs = Gui::Selection().getObjectsOfType(); Mesh::Feature* mesh = static_cast(objs.front()); Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(); if (!dlg) { dlg = new ReverseEngineeringGui::TaskSegmentation(mesh); } Gui::Control().showDialog(dlg); } bool CmdSegmentation::isActive() { if (Gui::Control().activeDialog()) { return false; } return Gui::Selection().countObjectsOfType() == 1; } DEF_STD_CMD_A(CmdSegmentationManual) CmdSegmentationManual::CmdSegmentationManual() : Command("Reen_SegmentationManual") { sAppModule = "Reen"; sGroup = QT_TR_NOOP("Reverse Engineering"); sMenuText = QT_TR_NOOP("Manual Segmentation…"); sToolTipText = QT_TR_NOOP("Creates mesh segments manually"); sWhatsThis = "Reen_SegmentationManual"; sStatusTip = sToolTipText; } void CmdSegmentationManual::activated(int) { Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(); if (!dlg) { dlg = new ReverseEngineeringGui::TaskSegmentationManual(); } Gui::Control().showDialog(dlg); } bool CmdSegmentationManual::isActive() { if (Gui::Control().activeDialog()) { return false; } return hasActiveDocument(); } DEF_STD_CMD_A(CmdSegmentationFromComponents) CmdSegmentationFromComponents::CmdSegmentationFromComponents() : Command("Reen_SegmentationFromComponents") { sAppModule = "Reen"; sGroup = QT_TR_NOOP("Reverse Engineering"); sMenuText = QT_TR_NOOP("From Components"); sToolTipText = QT_TR_NOOP("Creates mesh segments from components"); sWhatsThis = "Reen_SegmentationFromComponents"; sStatusTip = sToolTipText; } void CmdSegmentationFromComponents::activated(int) { std::vector sel = getSelection().getObjectsOfType(); App::Document* doc = App::GetApplication().getActiveDocument(); doc->openTransaction("Segmentation"); for (auto it : sel) { std::string internalname = "Segments_"; internalname += it->getNameInDocument(); auto* group = doc->addObject(internalname.c_str()); std::string labelname = "Segments "; labelname += it->Label.getValue(); group->Label.setValue(labelname); const Mesh::MeshObject& mesh = it->Mesh.getValue(); std::vector> comps = mesh.getComponents(); for (const auto& jt : comps) { std::unique_ptr segment(mesh.meshFromSegment(jt)); auto* feaSegm = group->addObject("Segment"); Mesh::MeshObject* feaMesh = feaSegm->Mesh.startEditing(); feaMesh->swap(*segment); feaSegm->Mesh.finishEditing(); } } doc->commitTransaction(); doc->recompute(); } bool CmdSegmentationFromComponents::isActive() { if (getSelection().countObjectsOfType() > 0) { return true; } return false; } DEF_STD_CMD_A(CmdMeshBoundary) CmdMeshBoundary::CmdMeshBoundary() : Command("Reen_MeshBoundary") { sAppModule = "Reen"; sGroup = QT_TR_NOOP("Reverse Engineering"); sMenuText = QT_TR_NOOP("Wire From Mesh Boundary…"); sToolTipText = QT_TR_NOOP("Creates a wire from mesh boundaries"); sWhatsThis = "Reen_Segmentation"; sStatusTip = sToolTipText; } void CmdMeshBoundary::activated(int) { std::vector objs = Gui::Selection().getObjectsOfType(); App::Document* document = App::GetApplication().getActiveDocument(); document->openTransaction("Wire from mesh"); for (auto it : objs) { const Mesh::MeshObject& mesh = it->Mesh.getValue(); std::list> bounds; MeshCore::MeshAlgorithm algo(mesh.getKernel()); algo.GetMeshBorders(bounds); BRep_Builder builder; TopoDS_Compound compound; builder.MakeCompound(compound); TopoDS_Shape shape; std::vector wires; for (const auto& bt : bounds) { BRepBuilderAPI_MakePolygon mkPoly; for (auto it = bt.rbegin(); it != bt.rend(); ++it) { mkPoly.Add(gp_Pnt(it->x, it->y, it->z)); } if (mkPoly.IsDone()) { builder.Add(compound, mkPoly.Wire()); wires.push_back(mkPoly.Wire()); } } try { shape = Part::FaceMakerCheese::makeFace(wires); } catch (...) { } if (!shape.IsNull()) { Part::Feature* shapeFea = document->addObject("Face from mesh"); shapeFea->Shape.setValue(shape); } else { Part::Feature* shapeFea = document->addObject("Wire from mesh"); shapeFea->Shape.setValue(compound); } } document->commitTransaction(); } bool CmdMeshBoundary::isActive() { return Gui::Selection().countObjectsOfType() > 0; } DEF_STD_CMD_A(CmdPoissonReconstruction) CmdPoissonReconstruction::CmdPoissonReconstruction() : Command("Reen_PoissonReconstruction") { sAppModule = "Reen"; sGroup = QT_TR_NOOP("Reverse Engineering"); sMenuText = QT_TR_NOOP("Poisson…"); sToolTipText = QT_TR_NOOP("Performs Poisson surface reconstruction"); sWhatsThis = "Reen_PoissonReconstruction"; sStatusTip = sToolTipText; } void CmdPoissonReconstruction::activated(int) { App::DocumentObjectT objT; std::vector obj = Gui::Selection().getObjectsOfType( Points::Feature::getClassTypeId() ); if (obj.size() != 1) { QMessageBox::warning( Gui::getMainWindow(), qApp->translate("Reen_ApproxSurface", "Wrong selection"), qApp->translate("Reen_ApproxSurface", "Select a single point cloud.") ); return; } objT = obj.front(); Gui::Control().showDialog(new ReenGui::TaskPoisson(objT)); } bool CmdPoissonReconstruction::isActive() { return (hasActiveDocument() && !Gui::Control().activeDialog()); } DEF_STD_CMD_A(CmdViewTriangulation) CmdViewTriangulation::CmdViewTriangulation() : Command("Reen_ViewTriangulation") { sAppModule = "Reen"; sGroup = QT_TR_NOOP("Reverse Engineering"); sMenuText = QT_TR_NOOP("Structured Point Clouds"); sToolTipText = QT_TR_NOOP("Triangulates structured point clouds"); sStatusTip = QT_TR_NOOP("Triangulation of structured point clouds"); sWhatsThis = "Reen_ViewTriangulation"; } void CmdViewTriangulation::activated(int) { std::vector obj = Gui::Selection().getObjectsOfType( Points::Structured::getClassTypeId() ); addModule(App, "ReverseEngineering"); openCommand(QT_TRANSLATE_NOOP("Command", "View triangulation")); try { for (const auto& it : obj) { App::DocumentObjectT objT(it); QString document = QString::fromStdString(objT.getDocumentPython()); QString object = QString::fromStdString(objT.getObjectPython()); QString command = QStringLiteral( "%1.addObject('Mesh::Feature', 'View mesh').Mesh " "= ReverseEngineering.viewTriangulation(" "Points=%2.Points," "Width=%2.Width," "Height=%2.Height)" ) .arg(document, object); runCommand(Doc, command.toLatin1()); } commitCommand(); updateActive(); } catch (const Base::Exception& e) { abortCommand(); QMessageBox::warning( Gui::getMainWindow(), qApp->translate("Reen_ViewTriangulation", "View triangulation failed"), QString::fromLatin1(e.what()) ); } } bool CmdViewTriangulation::isActive() { return (Gui::Selection().countObjectsOfType() > 0); } void CreateReverseEngineeringCommands() { Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager(); rcCmdMgr.addCommand(new CmdApproxCurve()); rcCmdMgr.addCommand(new CmdApproxSurface()); rcCmdMgr.addCommand(new CmdApproxPlane()); rcCmdMgr.addCommand(new CmdApproxCylinder()); rcCmdMgr.addCommand(new CmdApproxSphere()); rcCmdMgr.addCommand(new CmdApproxPolynomial()); rcCmdMgr.addCommand(new CmdSegmentation()); rcCmdMgr.addCommand(new CmdSegmentationManual()); rcCmdMgr.addCommand(new CmdSegmentationFromComponents()); rcCmdMgr.addCommand(new CmdMeshBoundary()); rcCmdMgr.addCommand(new CmdPoissonReconstruction()); rcCmdMgr.addCommand(new CmdViewTriangulation()); }