From df50dca01b47f4a26c709d7d40cfc7605578b564 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Wed, 20 Apr 2022 12:23:41 +0200 Subject: [PATCH] PythonConverter-Sketcher: A class to convert sketcher geometries and constraints into the commands to generate them =================================================================================================================== This class does not currently support all sketcher geometry and constraints, but it supports the most common types. --- src/Mod/Sketcher/App/CMakeLists.txt | 2 + src/Mod/Sketcher/App/PythonConverter.cpp | 412 +++++++++++++++++++++++ src/Mod/Sketcher/App/PythonConverter.h | 74 ++++ 3 files changed, 488 insertions(+) create mode 100644 src/Mod/Sketcher/App/PythonConverter.cpp create mode 100644 src/Mod/Sketcher/App/PythonConverter.h diff --git a/src/Mod/Sketcher/App/CMakeLists.txt b/src/Mod/Sketcher/App/CMakeLists.txt index b916c4c6e2..bff82ed3ee 100644 --- a/src/Mod/Sketcher/App/CMakeLists.txt +++ b/src/Mod/Sketcher/App/CMakeLists.txt @@ -64,6 +64,8 @@ SET(Features_SRCS SketchAnalysis.h SketchAnalysis.cpp Analyse.h + PythonConverter.cpp + PythonConverter.h ) SOURCE_GROUP("Features" FILES ${Features_SRCS}) diff --git a/src/Mod/Sketcher/App/PythonConverter.cpp b/src/Mod/Sketcher/App/PythonConverter.cpp new file mode 100644 index 0000000000..0437bd12c4 --- /dev/null +++ b/src/Mod/Sketcher/App/PythonConverter.cpp @@ -0,0 +1,412 @@ +/*************************************************************************** + * Copyright (c) 2022 Abdullah Tahiri * + * * + * 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 "PreCompiled.h" + +#ifndef _PreComp_ +#endif // #ifndef _PreComp_ + +#include + +#include + +#include +#include + +#include + +#include "PythonConverter.h" + + +using namespace Sketcher; + +std::string PythonConverter::convert(const Part::Geometry * geo) +{ + // "addGeometry(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)),%s)" + + std::string command; + auto sg = process(geo); + + command = boost::str(boost::format("addGeometry(%s,%s)\n") % + sg.creation % (sg.construction ? "True":"False")); + + return command; +} + +std::string PythonConverter::convert(const Sketcher::Constraint * constraint) +{ + // addConstraint(Sketcher.Constraint('Distance',%d,%f)) + std::string command; + auto cg = process(constraint); + + command = boost::str(boost::format("addConstraint(%s)\n") % cg); + + return command; +} + +std::string PythonConverter::convert(const std::string & doc, const std::vector & geos) +{ + std::string geolist = "geoList = []\n"; + std::string constrgeolist = "constrGeoList = []\n"; + + int ngeo = 0, nconstr = 0; + + for(auto geo : geos) { + auto sg = process(geo); + + if (sg.construction) { + constrgeolist = boost::str(boost::format("%s\nconstrGeoList.append(%s)\n") % + constrgeolist % sg.creation); + nconstr++; + } + else { + geolist = boost::str(boost::format("%s\ngeoList.append(%s)\n") % + geolist % sg.creation); + ngeo++; + } + } + + if(ngeo > 0) { + geolist = boost::str(boost::format("%s\n%s.addGeometry(geoList,%s)\ndel geoList\n") % + geolist % doc % "False"); + } + + if(nconstr > 0) { + constrgeolist = boost::str(boost::format("%s\n%s.addGeometry(constrGeoList,%s)\ndel constrGeoList") % + constrgeolist % doc % "True"); + } + + std::string command; + + if(ngeo > 0 && nconstr > 0) + command = geolist + constrgeolist; + else if (ngeo > 0) + command = std::move(geolist); + else if (nconstr > 0) + command = std::move(constrgeolist); + + return command; +} + +std::string PythonConverter::convert(const std::string & doc, const std::vector & constraints) +{ + if(constraints.size() == 1) { + auto cg = convert(constraints[0]); + + return boost::str(boost::format("%s.%s\n") % + doc % cg); + } + + std::string constraintlist = "constraintList = []"; + + for(auto constraint : constraints) { + auto cg = process(constraint); + + constraintlist = boost::str(boost::format("%s\nconstraintList.append(%s)") % + constraintlist % cg); + } + + if(constraints.size() > 0) { + constraintlist = boost::str(boost::format("%s\n%s.addConstraint(constraintList)\ndel constraintList\n") % + constraintlist % doc); + } + + return constraintlist; +} + +PythonConverter::SingleGeometry PythonConverter::process(const Part::Geometry * geo) +{ + static std::map> converterMap = { + { Part::GeomLineSegment::getClassTypeId(), + [](const Part::Geometry * geo){ + auto sgeo = static_cast(geo); + SingleGeometry sg; + sg.creation = boost::str(boost::format("Part.LineSegment(App.Vector(%f,%f,%f),App.Vector(%f,%f,%f))") % + sgeo->getStartPoint().x % sgeo->getStartPoint().y % sgeo->getStartPoint().z % + sgeo->getEndPoint().x % sgeo->getEndPoint().y % sgeo->getEndPoint().z); + sg.construction = Sketcher::GeometryFacade::getConstruction(geo); + return sg; + }}, + { Part::GeomArcOfCircle::getClassTypeId(), + [](const Part::Geometry * geo){ + auto arc = static_cast(geo); + SingleGeometry sg; + sg.creation = boost::str(boost::format("Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, %f), App.Vector(%f, %f, %f), %f), %f, %f)") % + arc->getCenter().x % arc->getCenter().y % arc->getCenter().z % + arc->getAxisDirection().x % arc->getAxisDirection().y % arc->getAxisDirection().z % + arc->getRadius() % arc->getFirstParameter() % arc->getLastParameter()); + sg.construction = Sketcher::GeometryFacade::getConstruction(geo); + return sg; + }}, + { Part::GeomPoint::getClassTypeId(), + [](const Part::Geometry* geo) { + auto sgeo = static_cast(geo); + SingleGeometry sg; + sg.creation = boost::str(boost::format("Part.Point(App.Vector(%f,%f,%f))") % + sgeo->getPoint().x % sgeo->getPoint().y % sgeo->getPoint().z); + sg.construction = Sketcher::GeometryFacade::getConstruction(geo); + return sg; + }}, + { Part::GeomEllipse::getClassTypeId(), + [](const Part::Geometry * geo){ + auto ellipse = static_cast(geo); + SingleGeometry sg; + auto periapsis = ellipse->getCenter() + ellipse->getMajorAxisDir() * ellipse->getMajorRadius(); + auto positiveB = ellipse->getCenter() + ellipse->getMinorAxisDir() * ellipse->getMinorRadius(); + auto center = ellipse->getCenter(); + sg.creation = boost::str(boost::format("Part.Ellipse(App.Vector(%f, %f, %f), App.Vector(%f, %f, %f), App.Vector(%f, %f, %f))") % + periapsis.x % periapsis.y % periapsis.z % + positiveB.x % positiveB.y % positiveB.z % + center.x % center.y % center.z); + sg.construction = Sketcher::GeometryFacade::getConstruction(geo); + return sg; + }}, + { Part::GeomCircle::getClassTypeId(), + [](const Part::Geometry * geo){ + auto circle = static_cast(geo); + SingleGeometry sg; + sg.creation = boost::str(boost::format("Part.Circle(App.Vector(%f, %f, %f), App.Vector(%f, %f, %f), %f)") % + circle->getCenter().x % circle->getCenter().y % circle->getCenter().z % + circle->getAxisDirection().x % circle->getAxisDirection().y % circle->getAxisDirection().z % + circle->getRadius()); + sg.construction = Sketcher::GeometryFacade::getConstruction(geo); + return sg; + }}, + }; + + auto result = converterMap.find(geo->getTypeId()); + + if( result == converterMap.end()) + THROWM(Base::ValueError, "PythonConverter: Geometry Type not supported") + + auto creator = result->second; + + return creator(geo); +} + +std::string PythonConverter::process(const Sketcher::Constraint * constraint) +{ + static std::map> converterMap = { + { Sketcher::Coincident, + [](const Sketcher::Constraint * constr){ + return boost::str(boost::format("Sketcher.Constraint('Coincident', %i, %i, %i, %i)") % + constr->First % static_cast(constr->FirstPos) % constr->Second % static_cast(constr->SecondPos)); + }}, + { Sketcher::Horizontal, + [](const Sketcher::Constraint * constr){ + if(constr->Second == GeoEnum::GeoUndef) { + return boost::str(boost::format("Sketcher.Constraint('Horizontal', %i)") % constr->First); + } + else { + return boost::str(boost::format("Sketcher.Constraint('Horizontal', %i, %i, %i, %i)") % + constr->First % static_cast(constr->FirstPos) % constr->Second % static_cast(constr->SecondPos)); + } + }}, + { Sketcher::Vertical, + [](const Sketcher::Constraint * constr){ + if(constr->Second == GeoEnum::GeoUndef) { + return boost::str(boost::format("Sketcher.Constraint('Vertical', %i)") % constr->First); + } + else { + return boost::str(boost::format("Sketcher.Constraint('Vertical', %i, %i, %i, %i)") % + constr->First % static_cast(constr->FirstPos) % constr->Second % static_cast(constr->SecondPos)); + } + }}, + { Sketcher::Block, + [](const Sketcher::Constraint * constr){ + return boost::str(boost::format("Sketcher.Constraint('Block', %i)") % constr->First); + }}, + { Sketcher::Tangent, + [](const Sketcher::Constraint * constr){ + if(constr->FirstPos == Sketcher::PointPos::none) { + return boost::str(boost::format("Sketcher.Constraint('Tangent', %i, %i)") % + constr->First % constr->Second); + } + else if(constr->SecondPos == Sketcher::PointPos::none){ + return boost::str(boost::format("Sketcher.Constraint('Tangent', %i, %i, %i)") % + constr->First % static_cast(constr->FirstPos) % constr->Second); + } + else { + return boost::str(boost::format("Sketcher.Constraint('Tangent', %i, %i, %i, %i)") % + constr->First % static_cast(constr->FirstPos) % constr->Second % static_cast(constr->SecondPos)); + } + }}, + { Sketcher::Parallel, + [](const Sketcher::Constraint * constr){ + return boost::str(boost::format("Sketcher.Constraint('Parallel', %i, %i)") % + constr->First % constr->Second); + }}, + { Sketcher::Perpendicular, + [](const Sketcher::Constraint * constr){ + if(constr->FirstPos == Sketcher::PointPos::none) { + return boost::str(boost::format("Sketcher.Constraint('Perpendicular', %i, %i)") % + constr->First % constr->Second); + } + else if(constr->SecondPos == Sketcher::PointPos::none){ + return boost::str(boost::format("Sketcher.Constraint('Perpendicular', %i, %i, %i)") % + constr->First % static_cast(constr->FirstPos) % constr->Second); + } + else { + return boost::str(boost::format("Sketcher.Constraint('Perpendicular', %i, %i, %i, %i)") % + constr->First % static_cast(constr->FirstPos) % constr->Second % static_cast(constr->SecondPos)); + } + }}, + { Sketcher::Equal, + [](const Sketcher::Constraint * constr){ + return boost::str(boost::format("Sketcher.Constraint('Equal', %i, %i)") % + constr->First % constr->Second); + }}, + { Sketcher::InternalAlignment, + [](const Sketcher::Constraint * constr){ + if(constr->InternalAlignmentIndex == EllipseMajorDiameter || + constr->InternalAlignmentIndex == EllipseMinorDiameter) { + return boost::str(boost::format("Sketcher.Constraint('InternalAlignment:%s', %i, %i)") % + constr->internalAlignmentTypeToString() % constr->First % constr->Second); + } + else if(constr->InternalAlignmentIndex == EllipseFocus1 || + constr->InternalAlignmentIndex == EllipseFocus2) { + return boost::str(boost::format("Sketcher.Constraint('InternalAlignment:%s', %i, %i, %i)") % + constr->internalAlignmentTypeToString() % constr->First % static_cast(constr->FirstPos) % constr->Second); + } + else if(constr->InternalAlignmentIndex == BSplineControlPoint) { + return boost::str(boost::format("Sketcher.Constraint('InternalAlignment:%s', %i, %i, %i, %i)") % + constr->internalAlignmentTypeToString() % constr->First % static_cast(constr->FirstPos) % + constr->Second % constr->InternalAlignmentIndex); + } + + THROWM(Base::ValueError, "PythonConverter: Constraint Alignment Type not supported") + }}, + { Sketcher::Distance, + [](const Sketcher::Constraint * constr){ + if(constr->Second == GeoEnum::GeoUndef){ + return boost::str(boost::format("Sketcher.Constraint('Distance', %i, %f)") % + constr->First % constr->getValue()); + } + else if(constr->SecondPos == Sketcher::PointPos::none){ + return boost::str(boost::format("Sketcher.Constraint('Distance', %i, %i, %i, %f)") % + constr->First % static_cast(constr->FirstPos) % constr->Second % constr->getValue()); + } + else { + return boost::str(boost::format("Sketcher.Constraint('Distance', %i, %i, %i, %i, %f)") % + constr->First % static_cast(constr->FirstPos) % constr->Second % + static_cast(constr->SecondPos) % constr->getValue()); + } + }}, + { Sketcher::Angle, + [](const Sketcher::Constraint * constr){ + if(constr->Second == GeoEnum::GeoUndef) { + return boost::str(boost::format("Sketcher.Constraint('Angle', %i, %f)") % + constr->First % constr->getValue()); + } + else if(constr->SecondPos == Sketcher::PointPos::none){ + return boost::str(boost::format("Sketcher.Constraint('Angle', %i, %i, %f)") % + constr->First % constr->Second % constr->getValue()); + } + else { + return boost::str(boost::format("Sketcher.Constraint('Angle', %i, %i, %i, %i, %f)") % + constr->First % static_cast(constr->FirstPos) % constr->Second % + static_cast(constr->SecondPos) % constr->getValue()); + } + }}, + { Sketcher::DistanceX, + [](const Sketcher::Constraint * constr){ + if(constr->Second == GeoEnum::GeoUndef) { + return boost::str(boost::format("Sketcher.Constraint('DistanceX', %i, %f)") % + constr->First % constr->getValue()); + } + else if(constr->SecondPos == Sketcher::PointPos::none){ + return boost::str(boost::format("Sketcher.Constraint('DistanceX', %i, %i, %f)") % + constr->First % static_cast(constr->FirstPos) % constr->getValue()); + } + else { + return boost::str(boost::format("Sketcher.Constraint('DistanceX', %i, %i, %i, %i, %f)") % + constr->First % static_cast(constr->FirstPos) % constr->Second % + static_cast(constr->SecondPos) % constr->getValue()); + } + }}, + { Sketcher::DistanceY, + [](const Sketcher::Constraint * constr){ + if(constr->Second == GeoEnum::GeoUndef) { + return boost::str(boost::format("Sketcher.Constraint('DistanceY', %i, %f)") % + constr->First % constr->getValue()); + } + else if(constr->SecondPos == Sketcher::PointPos::none){ + return boost::str(boost::format("Sketcher.Constraint('DistanceY', %i, %i, %f)") % + constr->First % static_cast(constr->FirstPos) % constr->getValue()); + } + else { + return boost::str(boost::format("Sketcher.Constraint('DistanceY', %i, %i, %i, %i, %f)") % + constr->First % static_cast(constr->FirstPos) % constr->Second % + static_cast(constr->SecondPos) % constr->getValue()); + } + }}, + { Sketcher::Radius, + [](const Sketcher::Constraint * constr){ + return boost::str(boost::format("Sketcher.Constraint('Radius', %i, %f)") % + constr->First % constr->getValue()); + }}, + { Sketcher::Diameter, + [](const Sketcher::Constraint * constr){ + return boost::str(boost::format("Sketcher.Constraint('Diameter', %i, %f)") % + constr->First % constr->getValue()); + }}, + { Sketcher::Weight, + [](const Sketcher::Constraint * constr){ + return boost::str(boost::format("Sketcher.Constraint('Weight', %i, %f)") % + constr->First % constr->getValue()); + }}, + { Sketcher::PointOnObject, + [](const Sketcher::Constraint * constr){ + return boost::str(boost::format("Sketcher.Constraint('PointOnObject', %i, %i, %i)") % + constr->First % static_cast(constr->FirstPos) % constr->Second); + }}, + { Sketcher::Symmetric, + [](const Sketcher::Constraint * constr){ + if(constr->ThirdPos==Sketcher::PointPos::none) { + return boost::str(boost::format("Sketcher.Constraint('Symmetric', %i, %i, %i, %i, %i)") % + constr->First % static_cast(constr->FirstPos) % constr->Second % static_cast(constr->SecondPos) % + constr->Third); + } + else { + return boost::str(boost::format("Sketcher.Constraint('Symmetric', %i, %i, %i, %i, %i, %i)") % + constr->First % static_cast(constr->FirstPos) % constr->Second % static_cast(constr->SecondPos) % + constr->Third % static_cast(constr->ThirdPos)); + } + }}, + { Sketcher::SnellsLaw, + [](const Sketcher::Constraint * constr){ + return boost::str(boost::format("Sketcher.Constraint('SnellsLaw', %i, %i, %i, %i, %i, %f)") % + constr->First % static_cast(constr->FirstPos) % constr->Second % static_cast(constr->SecondPos) % + constr->Third % constr->getValue()); + }}, + }; + + auto result = converterMap.find(constraint->Type); + + if( result == converterMap.end()) + THROWM(Base::ValueError, "PythonConverter: Constraint Type not supported") + + auto creator = result->second; + + return creator(constraint); +} diff --git a/src/Mod/Sketcher/App/PythonConverter.h b/src/Mod/Sketcher/App/PythonConverter.h new file mode 100644 index 0000000000..16c9edb4d0 --- /dev/null +++ b/src/Mod/Sketcher/App/PythonConverter.h @@ -0,0 +1,74 @@ +/*************************************************************************** + * Copyright (c) 2022 Abdullah Tahiri * + * * + * 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 * + * * + ***************************************************************************/ + + +#ifndef SKETCHER_PythonConverter_H +#define SKETCHER_PythonConverter_H + +namespace Part { + class Geometry; +} + +namespace Sketcher { + class Constraint; + +/** @brief Class for generating python code + * @details + * Given C++ structures, it generates the python code that should be written in the console to + * create such objects. + */ + +class SketcherExport PythonConverter { + + class SingleGeometry { + public: + std::string creation; + bool construction; + }; + +public: + + explicit PythonConverter() = delete; + ~PythonConverter() = delete; + + /// Convert a geometry into the string representing the command creating it + static std::string convert(const Part::Geometry * geo); + + /// Convert a vector of geometries into the string representing the command creating them + static std::string convert(const std::string & doc, const std::vector & geos); + + static std::string convert(const Sketcher::Constraint * constraint); + + static std::string convert(const std::string & doc, const std::vector & constraints); + +private: + static SingleGeometry process(const Part::Geometry * geo); + + static std::string process(const Sketcher::Constraint * constraint); + +}; + +} // namespace Sketcher + + +#endif // SKETCHER_PythonConverter_H +