// SPDX-License-Identifier: LGPL-2.1-or-later /**************************************************************************** * * * Copyright (c) 2023 Ondsel * * * * This file is part of FreeCAD. * * * * FreeCAD is free software: you can redistribute it and/or modify it * * under the terms of the GNU Lesser General Public License as * * published by the Free Software Foundation, either version 2.1 of the * * License, or (at your option) any later version. * * * * FreeCAD 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with FreeCAD. If not, see * * . * * * ***************************************************************************/ #ifndef ASSEMBLY_AssemblyObject_H #define ASSEMBLY_AssemblyObject_H #include #include #include #include #include #include #include namespace KCSolve { class IKCSolver; } // namespace KCSolve namespace App { class PropertyXLinkSub; } // namespace App namespace Base { class Placement; class Rotation; } // namespace Base namespace Assembly { class AssemblyLink; class JointGroup; class ViewGroup; enum class JointType; struct ObjRef { App::DocumentObject* obj; App::PropertyXLinkSub* ref; }; class AssemblyExport AssemblyObject: public App::Part { PROPERTY_HEADER_WITH_OVERRIDE(Assembly::AssemblyObject); public: AssemblyObject(); ~AssemblyObject() override; PyObject* getPyObject() override; /// returns the type name of the ViewProvider const char* getViewProviderName() const override { return "AssemblyGui::ViewProviderAssembly"; } App::DocumentObjectExecReturn* execute() override; void onChanged(const App::Property* prop) override; /* Solve the assembly. It will update first the joints, solve, update placements of the parts and redraw the joints Args : enableRedo : This store initial positions to enable undo while being in an active transaction (joint creation).*/ int solve(bool enableRedo = false, bool updateJCS = true); int generateSimulation(App::DocumentObject* sim); int updateForFrame(size_t index, bool updateJCS = true); size_t numberOfFrames(); void preDrag(std::vector dragParts); void doDragStep(); void postDrag(); void savePlacementsForUndo(); void undoSolve(); void resetSolver() { solver_.reset(); } void clearUndo(); void exportAsASMT(std::string fileName); /// Build the assembly constraint graph without solving. /// Returns an empty SolveContext if no parts are grounded. KCSolve::SolveContext getSolveContext(); bool validateNewPlacements(); void setNewPlacements(); static void redrawJointPlacements(std::vector joints); static void redrawJointPlacement(App::DocumentObject* joint); // This makes sure that LinkGroups or sub-assemblies have identity placements. void ensureIdentityPlacements(); int slidingPartIndex(App::DocumentObject* joint); JointGroup* getJointGroup() const; ViewGroup* getExplodedViewGroup() const; template T* getGroup(); std::vector getJoints( bool updateJCS = true, bool delBadJoints = false, bool subJoints = true ); std::vector getGroundedJoints(); std::vector getJointsOfObj(App::DocumentObject* obj); std::vector getJointsOfPart(App::DocumentObject* part); App::DocumentObject* getJointOfPartConnectingToGround( App::DocumentObject* part, std::string& name, const std::vector& excludeJoints = {} ); std::unordered_set getGroundedParts(); bool isJointConnectingPartToGround(App::DocumentObject* joint, const char* partPropName); bool isJointTypeConnecting(App::DocumentObject* joint); bool isObjInSetOfObjRefs(App::DocumentObject* obj, const std::vector& pairs); void removeUnconnectedJoints( std::vector& joints, std::unordered_set groundedObjs ); void traverseAndMarkConnectedParts( App::DocumentObject* currentPart, std::vector& connectedParts, const std::vector& joints ); std::vector getConnectedParts( App::DocumentObject* part, const std::vector& joints ); bool isPartGrounded(App::DocumentObject* part); bool isPartConnected(App::DocumentObject* part); std::vector getDownstreamParts( App::DocumentObject* part, App::DocumentObject* joint = nullptr ); App::DocumentObject* getUpstreamMovingPart( App::DocumentObject* part, App::DocumentObject*& joint, std::string& name, std::vector excludeJoints = {} ); double getObjMass(App::DocumentObject* obj); void setObjMasses(std::vector> objectMasses); std::vector getSubAssemblies(); std::vector getMotionsFromSimulation(App::DocumentObject* sim); bool isJointValid(App::DocumentObject* joint); bool isEmpty() const; int numberOfComponents() const; void updateSolveStatus(); inline int getLastDoF() const { return lastDoF; } inline bool getLastHasConflicts() const { return lastHasConflict; } inline bool getLastHasRedundancies() const { return lastHasRedundancies; } inline bool getLastHasPartialRedundancies() const { return lastHasPartialRedundancies; } inline bool getLastHasMalformedConstraints() const { return lastHasMalformedConstraints; } inline int getLastSolverStatus() const { return lastSolverStatus; } inline const std::vector& getLastConflicting() const { return lastConflictingJoints; } inline const std::vector& getLastRedundant() const { return lastRedundantJoints; } inline const std::vector& getLastPartiallyRedundant() const { return lastPartialRedundantJoints; } inline const std::vector& getLastMalformed() const { return lastMalformedJoints; } fastsignals::signal signalSolverUpdate; private: // ── Solver integration ───────────────────────────────────────── KCSolve::IKCSolver* getOrCreateSolver(); KCSolve::SolveContext buildSolveContext( const std::vector& joints, bool forSimulation = false, App::DocumentObject* sim = nullptr ); KCSolve::Transform computeMarkerTransform( App::DocumentObject* joint, const char* propRefName, const char* propPlcName ); struct RackPinionResult { std::string partIdI; KCSolve::Transform markerI; std::string partIdJ; KCSolve::Transform markerJ; }; RackPinionResult computeRackPinionMarkers(App::DocumentObject* joint); // ── Part ↔ solver ID mapping ─────────────────────────────────── // Maps a solver part ID to the FreeCAD objects it represents. // Multiple objects map to one ID when parts are bundled by Fixed joints. struct PartMapping { App::DocumentObject* obj; Base::Placement offset; // identity for primary, non-identity for bundled }; std::unordered_map> partIdToObjs_; std::unordered_map objToPartId_; // Register a part (and recursively its fixed-joint bundle when bundleFixed is set). // Returns the solver part ID. std::string registerPart(App::DocumentObject* obj); // ── Solver state ─────────────────────────────────────────────── std::unique_ptr solver_; KCSolve::SolveResult lastResult_; // ── Existing state (unchanged) ───────────────────────────────── std::vector> objMasses; std::vector draggedParts; std::vector> previousPositions; bool bundleFixed; int lastDoF; bool lastHasConflict; bool lastHasRedundancies; bool lastHasPartialRedundancies; bool lastHasMalformedConstraints; int lastSolverStatus; std::vector lastRedundantJoints; std::vector lastConflictingJoints; std::vector lastPartialRedundantJoints; std::vector lastMalformedJoints; }; } // namespace Assembly #endif // ASSEMBLY_AssemblyObject_H