Merge pull request #10764 from Ondsel-Development/asm_card9
[Assembly] Solve the assembly (Card 9).
4
cMake/FreeCAD_Helpers/SetupLibOndselSolver.cmake
Normal file
@@ -0,0 +1,4 @@
|
||||
macro(SetupOndselSolverCpp)
|
||||
# -------------------------------- OndselSolver --------------------------------
|
||||
find_package(OndselSolver REQUIRED)
|
||||
endmacro(SetupOndselSolverCpp)
|
||||
2
src/3rdParty/OndselSolver
vendored
@@ -464,6 +464,22 @@ float_type Vector3<float_type>::GetAngle(const Vector3& rcVect) const
|
||||
return float_type(acos(dot));
|
||||
}
|
||||
|
||||
template<class float_type>
|
||||
float_type Vector3<float_type>::GetAngleOriented(const Vector3& rcVect, const Vector3& norm) const
|
||||
{
|
||||
float_type angle = GetAngle(rcVect);
|
||||
|
||||
Vector3<float_type> crossProduct = Cross(rcVect);
|
||||
|
||||
// Use dot product to determine the sign
|
||||
float_type dot = crossProduct.Dot(norm);
|
||||
if (dot < 0) {
|
||||
angle = 2 * traits_type::pi() - angle;
|
||||
}
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
template<class float_type>
|
||||
void Vector3<float_type>::TransformToCoordinateSystem(const Vector3& rclBase,
|
||||
const Vector3& rclDirX,
|
||||
|
||||
@@ -194,6 +194,9 @@ public:
|
||||
bool IsNull() const;
|
||||
/// Get angle between both vectors. The returned value lies in the interval [0,pi].
|
||||
float_type GetAngle(const Vector3& rcVect) const;
|
||||
/// Get oriented angle between both vectors using a normal. The returned value lies in the
|
||||
/// interval [0,2*pi].
|
||||
float_type GetAngleOriented(const Vector3& rcVect, const Vector3& norm) const;
|
||||
/** Transforms this point to the coordinate system defined by origin \a rclBase,
|
||||
* vector \a vector rclDirX and vector \a vector rclDirY.
|
||||
* \note \a rclDirX must be perpendicular to \a rclDirY, i.e. \a rclDirX * \a rclDirY = 0..
|
||||
|
||||
@@ -1341,7 +1341,7 @@ void StdCmdDelete::activated(int iMsg)
|
||||
ViewProviderDocumentObject *vpedit = nullptr;
|
||||
if(editDoc)
|
||||
vpedit = dynamic_cast<ViewProviderDocumentObject*>(editDoc->getInEdit());
|
||||
if(vpedit) {
|
||||
if(vpedit && !vpedit->acceptDeletionsInEdit()) {
|
||||
for(auto &sel : Selection().getSelectionEx(editDoc->getDocument()->getName())) {
|
||||
if(sel.getObject() == vpedit->getObject()) {
|
||||
if (!sel.getSubNames().empty()) {
|
||||
|
||||
@@ -465,7 +465,7 @@ QStringList DlgSettingsWorkbenchesImp::getDisabledWorkbenches()
|
||||
ParameterGrp::handle hGrp;
|
||||
|
||||
hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches");
|
||||
disabled_wbs = QString::fromStdString(hGrp->GetASCII("Disabled", "NoneWorkbench,TestWorkbench,AssemblyWorkbench"));
|
||||
disabled_wbs = QString::fromStdString(hGrp->GetASCII("Disabled", "NoneWorkbench,TestWorkbench"));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
|
||||
unfiltered_disabled_wbs_list = disabled_wbs.split(QLatin1String(","), Qt::SkipEmptyParts);
|
||||
#else
|
||||
|
||||
@@ -2647,6 +2647,108 @@ SbVec2f View3DInventorViewer::getNormalizedPosition(const SbVec2s& pnt) const
|
||||
return {pX, pY};
|
||||
}
|
||||
|
||||
SbVec3f View3DInventorViewer::getPointOnXYPlaneOfPlacement(const SbVec2s& pnt, Base::Placement& plc) const
|
||||
{
|
||||
SbVec2f pnt2d = getNormalizedPosition(pnt);
|
||||
SoCamera* pCam = this->getSoRenderManager()->getCamera();
|
||||
|
||||
if (!pCam) {
|
||||
// return invalid point
|
||||
return {};
|
||||
}
|
||||
|
||||
SbViewVolume vol = pCam->getViewVolume();
|
||||
SbLine line;
|
||||
vol.projectPointToLine(pnt2d, line);
|
||||
|
||||
// Calculate the plane using plc
|
||||
Base::Rotation rot = plc.getRotation();
|
||||
Base::Vector3d normalVector = rot.multVec(Base::Vector3d(0, 0, 1));
|
||||
SbVec3f planeNormal(normalVector.x, normalVector.y, normalVector.z);
|
||||
|
||||
// Get the position and convert Base::Vector3d to SbVec3f
|
||||
Base::Vector3d pos = plc.getPosition();
|
||||
SbVec3f planePosition(pos.x, pos.y, pos.z);
|
||||
SbPlane xyPlane(planeNormal, planePosition);
|
||||
|
||||
SbVec3f pt;
|
||||
if (xyPlane.intersect(line, pt)) {
|
||||
return pt; // Intersection point on the XY plane
|
||||
}
|
||||
else {
|
||||
// No intersection found
|
||||
return {};
|
||||
}
|
||||
|
||||
return pt;
|
||||
}
|
||||
|
||||
SbVec3f projectPointOntoPlane(const SbVec3f& point, const SbPlane& plane) {
|
||||
SbVec3f planeNormal = plane.getNormal();
|
||||
float d = plane.getDistanceFromOrigin();
|
||||
float distance = planeNormal.dot(point) + d;
|
||||
return point - planeNormal * distance;
|
||||
}
|
||||
|
||||
// Project a line onto a plane
|
||||
SbLine projectLineOntoPlane(const SbVec3f& p1, const SbVec3f& p2, const SbPlane& plane) {
|
||||
SbVec3f projectedPoint1 = projectPointOntoPlane(p1, plane);
|
||||
SbVec3f projectedPoint2 = projectPointOntoPlane(p2, plane);
|
||||
return SbLine(projectedPoint1, projectedPoint2);
|
||||
}
|
||||
|
||||
SbVec3f intersection(const SbVec3f& p11, const SbVec3f& p12, const SbVec3f& p21, const SbVec3f& p22)
|
||||
{
|
||||
SbVec3f da = p12 - p11;
|
||||
SbVec3f db = p22 - p21;
|
||||
SbVec3f dc = p21 - p11;
|
||||
|
||||
double s = (dc.cross(db)).dot(da.cross(db)) / da.cross(db).sqrLength();
|
||||
return p11 + da * s;
|
||||
}
|
||||
|
||||
SbVec3f View3DInventorViewer::getPointOnLine(const SbVec2s& pnt, const SbVec3f& axisCenter, const SbVec3f& axis) const
|
||||
{
|
||||
SbVec2f pnt2d = getNormalizedPosition(pnt);
|
||||
SoCamera* pCam = this->getSoRenderManager()->getCamera();
|
||||
|
||||
if (!pCam) {
|
||||
// return invalid point
|
||||
return {};
|
||||
}
|
||||
|
||||
// First we get pnt projection on the focal plane
|
||||
SbViewVolume vol = pCam->getViewVolume();
|
||||
|
||||
float nearDist = pCam->nearDistance.getValue();
|
||||
float farDist = pCam->farDistance.getValue();
|
||||
float focalDist = pCam->focalDistance.getValue();
|
||||
|
||||
if (focalDist < nearDist || focalDist > farDist) {
|
||||
focalDist = 0.5F * (nearDist + farDist); // NOLINT
|
||||
}
|
||||
|
||||
SbLine line;
|
||||
SbVec3f pt, ptOnFocalPlaneAndOnLine, ptOnFocalPlane;
|
||||
SbPlane focalPlane = vol.getPlane(focalDist);
|
||||
vol.projectPointToLine(pnt2d, line);
|
||||
focalPlane.intersect(line, ptOnFocalPlane);
|
||||
|
||||
SbLine projectedLine = projectLineOntoPlane(axisCenter, axisCenter + axis, focalPlane);
|
||||
ptOnFocalPlaneAndOnLine = projectedLine.getClosestPoint(ptOnFocalPlane);
|
||||
|
||||
// now we need the intersection point between
|
||||
// - the line passing by ptOnFocalPlaneAndOnLine normal to focalPlane
|
||||
// - The line (axisCenter, axisCenter + axis)
|
||||
|
||||
// Line normal to focal plane through ptOnFocalPlane
|
||||
SbLine normalLine(ptOnFocalPlane, ptOnFocalPlane + focalPlane.getNormal());
|
||||
SbLine axisLine(axisCenter, axisCenter + axis);
|
||||
pt = intersection(ptOnFocalPlane, ptOnFocalPlane + focalPlane.getNormal(), axisCenter, axisCenter + axis);
|
||||
|
||||
return pt;
|
||||
}
|
||||
|
||||
SbVec3f View3DInventorViewer::getPointOnFocalPlane(const SbVec2s& pnt) const
|
||||
{
|
||||
SbVec2f pnt2d = getNormalizedPosition(pnt);
|
||||
@@ -3975,4 +4077,4 @@ void View3DInventorViewer::dragLeaveEvent(QDragLeaveEvent* ev)
|
||||
inherited::dragLeaveEvent(ev);
|
||||
}
|
||||
|
||||
#include "moc_View3DInventorViewer.cpp"
|
||||
#include "moc_View3DInventorViewer.cpp" // NOLINT
|
||||
|
||||
@@ -318,6 +318,12 @@ public:
|
||||
/** Returns the 3d point on the focal plane to the given 2d point. */
|
||||
SbVec3f getPointOnFocalPlane(const SbVec2s&) const;
|
||||
|
||||
/** Returns the 3d point on a line to the given 2d point. */
|
||||
SbVec3f getPointOnLine(const SbVec2s&, const SbVec3f& axisCenter, const SbVec3f& axis) const;
|
||||
|
||||
/** Returns the 3d point on the XY plane of a placement to the given 2d point. */
|
||||
SbVec3f getPointOnXYPlaneOfPlacement(const SbVec2s&, Base::Placement&) const;
|
||||
|
||||
/** Returns the 2d coordinates on the viewport to the given 3d point. */
|
||||
SbVec2s getPointOnViewport(const SbVec3f&) const;
|
||||
|
||||
|
||||
@@ -94,6 +94,8 @@ public:
|
||||
App::DocumentObject *getObject() const {return pcObject;}
|
||||
/// Asks the view provider if the given object can be deleted.
|
||||
bool canDelete(App::DocumentObject* obj) const override;
|
||||
/// Ask the view provider if it accepts object deletions while in edit
|
||||
virtual bool acceptDeletionsInEdit() { return false; }
|
||||
/// Get the GUI document to this ViewProvider object
|
||||
Gui::Document* getDocument() const;
|
||||
/// Get the python wrapper for that ViewProvider
|
||||
|
||||
@@ -621,7 +621,7 @@ class Addon:
|
||||
wbName = self.get_workbench_name()
|
||||
|
||||
# Add the wb to the list of disabled if it was not already
|
||||
disabled_wbs = pref.GetString("Disabled", "NoneWorkbench,TestWorkbench,AssemblyWorkbench")
|
||||
disabled_wbs = pref.GetString("Disabled", "NoneWorkbench,TestWorkbench")
|
||||
# print(f"start disabling {disabled_wbs}")
|
||||
disabled_wbs_list = disabled_wbs.split(",")
|
||||
if not (wbName in disabled_wbs_list):
|
||||
@@ -652,7 +652,7 @@ class Addon:
|
||||
def remove_from_disabled_wbs(self, wbName: str):
|
||||
pref = fci.ParamGet("User parameter:BaseApp/Preferences/Workbenches")
|
||||
|
||||
disabled_wbs = pref.GetString("Disabled", "NoneWorkbench,TestWorkbench,AssemblyWorkbench")
|
||||
disabled_wbs = pref.GetString("Disabled", "NoneWorkbench,TestWorkbench")
|
||||
# print(f"start enabling : {disabled_wbs}")
|
||||
disabled_wbs_list = disabled_wbs.split(",")
|
||||
disabled_wbs = ""
|
||||
|
||||
63
src/Mod/Assembly/App/AppAssembly.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#include <Base/Console.h>
|
||||
#include <Base/Interpreter.h>
|
||||
#include <Base/PyObjectBase.h>
|
||||
|
||||
#include "AssemblyObject.h"
|
||||
#include "JointGroup.h"
|
||||
|
||||
|
||||
namespace Assembly
|
||||
{
|
||||
extern PyObject* initModule();
|
||||
}
|
||||
|
||||
/* Python entry */
|
||||
PyMOD_INIT_FUNC(AssemblyApp)
|
||||
{
|
||||
// load dependent module
|
||||
try {
|
||||
Base::Interpreter().runString("import Part");
|
||||
}
|
||||
catch (const Base::Exception& e) {
|
||||
PyErr_SetString(PyExc_ImportError, e.what());
|
||||
PyMOD_Return(nullptr);
|
||||
}
|
||||
|
||||
PyObject* mod = Assembly::initModule();
|
||||
Base::Console().Log("Loading Assembly module... done\n");
|
||||
|
||||
|
||||
// NOTE: To finish the initialization of our own type objects we must
|
||||
// call PyType_Ready, otherwise we run into a segmentation fault, later on.
|
||||
// This function is responsible for adding inherited slots from a type's base class.
|
||||
|
||||
Assembly::AssemblyObject ::init();
|
||||
Assembly::JointGroup ::init();
|
||||
|
||||
PyMOD_Return(mod);
|
||||
}
|
||||
47
src/Mod/Assembly/App/AppAssemblyPy.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#include <Base/Interpreter.h>
|
||||
#include <Base/Tools.h>
|
||||
|
||||
|
||||
namespace Assembly
|
||||
{
|
||||
class Module: public Py::ExtensionModule<Module>
|
||||
{
|
||||
public:
|
||||
Module()
|
||||
: Py::ExtensionModule<Module>("AssemblyApp")
|
||||
{
|
||||
initialize("This module is the Assembly module."); // register with Python
|
||||
}
|
||||
};
|
||||
|
||||
PyObject* initModule()
|
||||
{
|
||||
return Base::Interpreter().addModule(new Module);
|
||||
}
|
||||
|
||||
} // namespace Assembly
|
||||
1707
src/Mod/Assembly/App/AssemblyObject.cpp
Normal file
213
src/Mod/Assembly/App/AssemblyObject.h
Normal file
@@ -0,0 +1,213 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#ifndef ASSEMBLY_AssemblyObject_H
|
||||
#define ASSEMBLY_AssemblyObject_H
|
||||
|
||||
|
||||
#include <GeomAbs_CurveType.hxx>
|
||||
#include <GeomAbs_SurfaceType.hxx>
|
||||
|
||||
#include <Mod/Assembly/AssemblyGlobal.h>
|
||||
|
||||
#include <App/FeaturePython.h>
|
||||
#include <App/Part.h>
|
||||
#include <App/PropertyLinks.h>
|
||||
|
||||
namespace MbD
|
||||
{
|
||||
class ASMTPart;
|
||||
class ASMTAssembly;
|
||||
class ASMTJoint;
|
||||
class ASMTMarker;
|
||||
class ASMTPart;
|
||||
} // namespace MbD
|
||||
|
||||
namespace Base
|
||||
{
|
||||
class Placement;
|
||||
class Rotation;
|
||||
} // namespace Base
|
||||
|
||||
|
||||
namespace Assembly
|
||||
{
|
||||
|
||||
class JointGroup;
|
||||
|
||||
// This enum has to be the same as the one in JointObject.py
|
||||
enum class JointType
|
||||
{
|
||||
Fixed,
|
||||
Revolute,
|
||||
Cylindrical,
|
||||
Slider,
|
||||
Ball,
|
||||
Distance
|
||||
};
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
/* 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);
|
||||
void preDrag(std::vector<App::DocumentObject*> dragParts);
|
||||
void doDragStep();
|
||||
void postDrag();
|
||||
void savePlacementsForUndo();
|
||||
void undoSolve();
|
||||
void clearUndo();
|
||||
|
||||
void exportAsASMT(std::string fileName);
|
||||
|
||||
void setNewPlacements();
|
||||
void recomputeJointPlacements(std::vector<App::DocumentObject*> joints);
|
||||
void redrawJointPlacements(std::vector<App::DocumentObject*> joints);
|
||||
|
||||
|
||||
// Ondsel Solver interface
|
||||
std::shared_ptr<MbD::ASMTAssembly> makeMbdAssembly();
|
||||
std::shared_ptr<MbD::ASMTPart>
|
||||
makeMbdPart(std::string& name, Base::Placement plc = Base::Placement(), double mass = 1.0);
|
||||
std::shared_ptr<MbD::ASMTPart> getMbDPart(App::DocumentObject* obj);
|
||||
std::shared_ptr<MbD::ASMTMarker> makeMbdMarker(std::string& name, Base::Placement& plc);
|
||||
std::vector<std::shared_ptr<MbD::ASMTJoint>> makeMbdJoint(App::DocumentObject* joint);
|
||||
std::shared_ptr<MbD::ASMTJoint> makeMbdJointOfType(App::DocumentObject* joint,
|
||||
JointType jointType);
|
||||
std::shared_ptr<MbD::ASMTJoint> makeMbdJointDistance(App::DocumentObject* joint);
|
||||
std::shared_ptr<MbD::ASMTJoint> makeMbdJointDistanceFaceVertex(App::DocumentObject* joint);
|
||||
std::shared_ptr<MbD::ASMTJoint> makeMbdJointDistanceEdgeVertex(App::DocumentObject* joint);
|
||||
std::shared_ptr<MbD::ASMTJoint> makeMbdJointDistanceFaceEdge(App::DocumentObject* joint);
|
||||
std::shared_ptr<MbD::ASMTJoint> makeMbdJointDistanceEdgeEdge(App::DocumentObject* joint);
|
||||
std::shared_ptr<MbD::ASMTJoint> makeMbdJointDistanceFaceFace(App::DocumentObject* joint);
|
||||
std::string handleOneSideOfJoint(App::DocumentObject* joint,
|
||||
const char* propObjLinkName,
|
||||
const char* propPartName,
|
||||
const char* propPlcName);
|
||||
|
||||
void jointParts(std::vector<App::DocumentObject*> joints);
|
||||
JointGroup* getJointGroup();
|
||||
std::vector<App::DocumentObject*> getJoints(bool updateJCS = true);
|
||||
std::vector<App::DocumentObject*> getGroundedJoints();
|
||||
std::vector<App::DocumentObject*> getJointsOfObj(App::DocumentObject* obj);
|
||||
std::vector<App::DocumentObject*> getJointsOfPart(App::DocumentObject* part);
|
||||
App::DocumentObject* getJointOfPartConnectingToGround(App::DocumentObject* part,
|
||||
std::string& name);
|
||||
std::vector<App::DocumentObject*> getGroundedParts();
|
||||
std::vector<App::DocumentObject*> fixGroundedParts();
|
||||
void fixGroundedPart(App::DocumentObject* obj, Base::Placement& plc, std::string& jointName);
|
||||
|
||||
bool isJointConnectingPartToGround(App::DocumentObject* joint, const char* partPropName);
|
||||
|
||||
void removeUnconnectedJoints(std::vector<App::DocumentObject*>& joints,
|
||||
std::vector<App::DocumentObject*> groundedObjs);
|
||||
void traverseAndMarkConnectedParts(App::DocumentObject* currentPart,
|
||||
std::set<App::DocumentObject*>& connectedParts,
|
||||
const std::vector<App::DocumentObject*>& joints);
|
||||
std::vector<App::DocumentObject*>
|
||||
getConnectedParts(App::DocumentObject* part, const std::vector<App::DocumentObject*>& joints);
|
||||
bool isPartGrounded(App::DocumentObject* part);
|
||||
bool isPartConnected(App::DocumentObject* part);
|
||||
|
||||
std::vector<App::DocumentObject*> getDownstreamParts(App::DocumentObject* part,
|
||||
App::DocumentObject* joint);
|
||||
std::vector<App::DocumentObject*> getUpstreamParts(App::DocumentObject* part, int limit = 0);
|
||||
App::DocumentObject* getUpstreamMovingPart(App::DocumentObject* part);
|
||||
|
||||
double getObjMass(App::DocumentObject* obj);
|
||||
void setObjMasses(std::vector<std::pair<App::DocumentObject*, double>> objectMasses);
|
||||
|
||||
|
||||
private:
|
||||
std::shared_ptr<MbD::ASMTAssembly> mbdAssembly;
|
||||
|
||||
std::unordered_map<App::DocumentObject*, std::shared_ptr<MbD::ASMTPart>> objectPartMap;
|
||||
std::vector<std::pair<App::DocumentObject*, double>> objMasses;
|
||||
std::vector<std::shared_ptr<MbD::ASMTPart>> dragMbdParts;
|
||||
|
||||
std::vector<std::pair<App::DocumentObject*, Base::Placement>> previousPositions;
|
||||
|
||||
// void handleChangedPropertyType(Base::XMLReader &reader, const char *TypeName, App::Property
|
||||
// *prop) override;
|
||||
|
||||
public:
|
||||
// ---------------- Utils -------------------
|
||||
// Can't put the functions by themselves in AssemblyUtils.cpp :
|
||||
// see https://forum.freecad.org/viewtopic.php?p=729577#p729577
|
||||
|
||||
void swapJCS(App::DocumentObject* joint);
|
||||
|
||||
bool isEdgeType(App::DocumentObject* obj, const char* elName, GeomAbs_CurveType type);
|
||||
bool isFaceType(App::DocumentObject* obj, const char* elName, GeomAbs_SurfaceType type);
|
||||
double getFaceRadius(App::DocumentObject* obj, const char* elName);
|
||||
double getEdgeRadius(App::DocumentObject* obj, const char* elName);
|
||||
|
||||
// getters to get from properties
|
||||
static void setJointActivated(App::DocumentObject* joint, bool val);
|
||||
static bool getJointActivated(App::DocumentObject* joint);
|
||||
static double getJointDistance(App::DocumentObject* joint);
|
||||
static JointType getJointType(App::DocumentObject* joint);
|
||||
static const char* getElementFromProp(App::DocumentObject* obj, const char* propName);
|
||||
static std::string getElementTypeFromProp(App::DocumentObject* obj, const char* propName);
|
||||
static App::DocumentObject* getLinkObjFromProp(App::DocumentObject* joint,
|
||||
const char* propName);
|
||||
static App::DocumentObject*
|
||||
getObjFromNameProp(App::DocumentObject* joint, const char* pObjName, const char* pPart);
|
||||
static App::DocumentObject*
|
||||
getLinkedObjFromNameProp(App::DocumentObject* joint, const char* pObjName, const char* pPart);
|
||||
static Base::Placement getPlacementFromProp(App::DocumentObject* obj, const char* propName);
|
||||
static bool getTargetPlacementRelativeTo(Base::Placement& foundPlc,
|
||||
App::DocumentObject* targetObj,
|
||||
App::DocumentObject* part,
|
||||
App::DocumentObject* container,
|
||||
bool inContainerBranch,
|
||||
bool ignorePlacement = false);
|
||||
static Base::Placement getGlobalPlacement(App::DocumentObject* targetObj,
|
||||
App::DocumentObject* container = nullptr);
|
||||
static Base::Placement getGlobalPlacement(App::DocumentObject* joint,
|
||||
const char* targetObj,
|
||||
const char* container = "");
|
||||
};
|
||||
|
||||
// using AssemblyObjectPython = App::FeaturePythonT<AssemblyObject>;
|
||||
|
||||
} // namespace Assembly
|
||||
|
||||
|
||||
#endif // ASSEMBLY_AssemblyObject_H
|
||||
91
src/Mod/Assembly/App/AssemblyObjectPy.xml
Normal file
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<GenerateModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="generateMetaModel_Module.xsd">
|
||||
<PythonExport
|
||||
Father="PartPy"
|
||||
Name="AssemblyObjectPy"
|
||||
Twin="AssemblyObject"
|
||||
TwinPointer="AssemblyObject"
|
||||
Include="Mod/Assembly/App/AssemblyObject.h"
|
||||
Namespace="Assembly"
|
||||
FatherInclude="App/PartPy.h"
|
||||
FatherNamespace="App">
|
||||
<Documentation>
|
||||
<Author Licence="LGPL" Name="Ondsel" EMail="development@ondsel.com" />
|
||||
<UserDocu>This class handles document objects in Assembly</UserDocu>
|
||||
</Documentation>
|
||||
<Methode Name="solve">
|
||||
<Documentation>
|
||||
<UserDocu>
|
||||
Solve the assembly and update part placements.
|
||||
|
||||
solve(enableRedo=False) -> int
|
||||
|
||||
Args:
|
||||
enableRedo: Whether the solve save the initial position of parts
|
||||
to enable undoing it even without a transaction.
|
||||
Defaults to `False` ie the solve cannot be undone if called
|
||||
outside of a transaction.
|
||||
|
||||
Returns:
|
||||
0 in case of success, otherwise the following codes in this order of
|
||||
priority:
|
||||
-6 if no parts are fixed.
|
||||
-4 if over-constrained,
|
||||
-3 if conflicting constraints,
|
||||
-5 if malformed constraints
|
||||
-1 if solver error,
|
||||
-2 if redundant constraints.
|
||||
</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<Methode Name="undoSolve">
|
||||
<Documentation>
|
||||
<UserDocu>
|
||||
Undo the last solve of the assembly and return part placements to their initial position.
|
||||
|
||||
undoSolve()
|
||||
|
||||
Returns: None
|
||||
</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<Methode Name="clearUndo">
|
||||
<Documentation>
|
||||
<UserDocu>
|
||||
Clear the registered undo positions.
|
||||
|
||||
clearUndo()
|
||||
|
||||
Returns: None
|
||||
</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<Methode Name="isPartConnected">
|
||||
<Documentation>
|
||||
<UserDocu>
|
||||
Check if a part is connected to the ground through joints.
|
||||
|
||||
isPartConnected(obj) -> bool
|
||||
|
||||
Args: document object to check.
|
||||
|
||||
Returns: True if part is connected to ground
|
||||
</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<Methode Name="exportAsASMT">
|
||||
<Documentation>
|
||||
<UserDocu>
|
||||
Export the assembly in a text format called ASMT.
|
||||
|
||||
exportAsASMT(fileName:str)
|
||||
|
||||
Args:
|
||||
fileName: The name of the file where the ASMT will be exported.
|
||||
</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
|
||||
<CustomAttributes />
|
||||
</PythonExport>
|
||||
</GenerateModel>
|
||||
118
src/Mod/Assembly/App/AssemblyObjectPyImp.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2014 Jürgen Riegel <juergen.riegel@web.de> *
|
||||
* *
|
||||
* 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"
|
||||
|
||||
// inclusion of the generated files (generated out of AssemblyObject.xml)
|
||||
#include "AssemblyObjectPy.h"
|
||||
#include "AssemblyObjectPy.cpp"
|
||||
|
||||
using namespace Assembly;
|
||||
|
||||
// returns a string which represents the object e.g. when printed in python
|
||||
std::string AssemblyObjectPy::representation() const
|
||||
{
|
||||
return {"<Assembly object>"};
|
||||
}
|
||||
|
||||
PyObject* AssemblyObjectPy::getCustomAttributes(const char* /*attr*/) const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int AssemblyObjectPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyObject* AssemblyObjectPy::solve(PyObject* args)
|
||||
{
|
||||
PyObject* enableUndoPy;
|
||||
bool enableUndo;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O!", &PyBool_Type, &enableUndoPy)) {
|
||||
PyErr_Clear();
|
||||
if (!PyArg_ParseTuple(args, "")) {
|
||||
return nullptr;
|
||||
}
|
||||
else {
|
||||
enableUndo = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
enableUndo = Base::asBoolean(enableUndoPy);
|
||||
}
|
||||
|
||||
int ret = this->getAssemblyObjectPtr()->solve(enableUndo);
|
||||
return Py_BuildValue("i", ret);
|
||||
}
|
||||
|
||||
PyObject* AssemblyObjectPy::undoSolve(PyObject* args)
|
||||
{
|
||||
if (!PyArg_ParseTuple(args, "")) {
|
||||
return nullptr;
|
||||
}
|
||||
this->getAssemblyObjectPtr()->undoSolve();
|
||||
Py_Return;
|
||||
}
|
||||
|
||||
PyObject* AssemblyObjectPy::clearUndo(PyObject* args)
|
||||
{
|
||||
if (!PyArg_ParseTuple(args, "")) {
|
||||
return nullptr;
|
||||
}
|
||||
this->getAssemblyObjectPtr()->clearUndo();
|
||||
Py_Return;
|
||||
}
|
||||
|
||||
PyObject* AssemblyObjectPy::isPartConnected(PyObject* args)
|
||||
{
|
||||
PyObject* pyobj;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &pyobj)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto* obj = static_cast<App::DocumentObjectPy*>(pyobj)->getDocumentObjectPtr();
|
||||
bool ok = this->getAssemblyObjectPtr()->isPartConnected(obj);
|
||||
return Py_BuildValue("O", (ok ? Py_True : Py_False));
|
||||
}
|
||||
|
||||
PyObject* AssemblyObjectPy::exportAsASMT(PyObject* args)
|
||||
{
|
||||
char* utf8Name;
|
||||
if (!PyArg_ParseTuple(args, "et", "utf-8", &utf8Name)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string fileName = utf8Name;
|
||||
PyMem_Free(utf8Name);
|
||||
|
||||
if (fileName.empty()) {
|
||||
PyErr_SetString(PyExc_ValueError, "Passed string is empty");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
this->getAssemblyObjectPtr()->exportAsASMT(fileName);
|
||||
|
||||
Py_Return;
|
||||
}
|
||||
249
src/Mod/Assembly/App/AssemblyUtils.cpp
Normal file
@@ -0,0 +1,249 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
#ifndef _PreComp_
|
||||
#endif
|
||||
|
||||
#include <App/Application.h>
|
||||
#include <App/Document.h>
|
||||
#include <App/DocumentObject.h>
|
||||
#include <App/PropertyStandard.h>
|
||||
// #include <App/DocumentObjectGroup.h>
|
||||
#include <App/Link.h>
|
||||
// #include <Base/Console.h>
|
||||
#include <Base/Placement.h>
|
||||
#include <Base/Tools.h>
|
||||
#include <Base/Interpreter.h>
|
||||
|
||||
#include <Mod/Part/App/PartFeature.h>
|
||||
|
||||
#include "AssemblyUtils.h"
|
||||
|
||||
// ======================================= Utils ======================================
|
||||
/*
|
||||
namespace Assembly
|
||||
{
|
||||
|
||||
Base::Placement getPlacementFromProp(App::DocumentObject* obj, const char* propName)
|
||||
{
|
||||
Base::Placement plc = Base::Placement();
|
||||
auto* propPlacement = dynamic_cast<App::PropertyPlacement*>(obj->getPropertyByName(propName));
|
||||
if (propPlacement) {
|
||||
plc = propPlacement->getValue();
|
||||
}
|
||||
return plc;
|
||||
}
|
||||
|
||||
/* // Currently unused
|
||||
Base::Placement* getTargetPlacementRelativeTo(
|
||||
App::DocumentObject* targetObj, App::DocumentObject* part, App::DocumentObject* container,
|
||||
bool inContainerBranch, bool ignorePlacement = false)
|
||||
{
|
||||
inContainerBranch = inContainerBranch || (!ignorePlacement && part == container);
|
||||
|
||||
Base::Console().Warning("sub --------------\n");
|
||||
if (targetObj == part && inContainerBranch && !ignorePlacement) {
|
||||
Base::Console().Warning("found0\n");
|
||||
return &getPlacementFromProp(targetObj, "Placement");
|
||||
}
|
||||
|
||||
if (auto group = dynamic_cast<App::DocumentObjectGroup*>(part)) {
|
||||
for (auto& obj : group->getOutList()) {
|
||||
auto foundPlacement = getTargetPlacementRelativeTo(
|
||||
targetObj, obj, container, inContainerBranch, ignorePlacement
|
||||
);
|
||||
if (foundPlacement != nullptr) {
|
||||
return foundPlacement;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto assembly = dynamic_cast<AssemblyObject*>(part)) {
|
||||
Base::Console().Warning("h3\n");
|
||||
for (auto& obj : assembly->getOutList()) {
|
||||
auto foundPlacement = getTargetPlacementRelativeTo(
|
||||
targetObj, obj, container, inContainerBranch
|
||||
);
|
||||
if (foundPlacement == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ignorePlacement) {
|
||||
*foundPlacement = getPlacementFromProp(part, "Placement") * *foundPlacement;
|
||||
}
|
||||
|
||||
Base::Console().Warning("found\n");
|
||||
return foundPlacement;
|
||||
}
|
||||
}
|
||||
else if (auto link = dynamic_cast<App::Link*>(part)) {
|
||||
Base::Console().Warning("h4\n");
|
||||
auto linked_obj = link->getLinkedObject();
|
||||
|
||||
if (dynamic_cast<App::Part*>(linked_obj) || dynamic_cast<AssemblyObject*>(linked_obj)) {
|
||||
for (auto& obj : linked_obj->getOutList()) {
|
||||
auto foundPlacement = getTargetPlacementRelativeTo(
|
||||
targetObj, obj, container, inContainerBranch
|
||||
);
|
||||
if (foundPlacement == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
*foundPlacement = getPlacementFromProp(link, "Placement") * *foundPlacement;
|
||||
return foundPlacement;
|
||||
}
|
||||
}
|
||||
|
||||
auto foundPlacement = getTargetPlacementRelativeTo(
|
||||
targetObj, linked_obj, container, inContainerBranch, true
|
||||
);
|
||||
|
||||
if (foundPlacement != nullptr && !ignorePlacement) {
|
||||
*foundPlacement = getPlacementFromProp(link, "Placement") * *foundPlacement;
|
||||
}
|
||||
|
||||
Base::Console().Warning("found2\n");
|
||||
return foundPlacement;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Base::Placement getGlobalPlacement(App::DocumentObject* targetObj, App::DocumentObject* container =
|
||||
nullptr) { bool inContainerBranch = container == nullptr; auto rootObjects =
|
||||
App::GetApplication().getActiveDocument()->getRootObjects(); for (auto& part : rootObjects) { auto
|
||||
foundPlacement = getTargetPlacementRelativeTo(targetObj, part, container, inContainerBranch); if
|
||||
(foundPlacement != nullptr) { Base::Placement plc(foundPlacement->toMatrix()); return plc;
|
||||
}
|
||||
}
|
||||
|
||||
return Base::Placement();
|
||||
}
|
||||
*/
|
||||
/*
|
||||
double getJointDistance(App::DocumentObject* joint)
|
||||
{
|
||||
double distance = 0.0;
|
||||
|
||||
auto* prop = dynamic_cast<App::PropertyFloat*>(joint->getPropertyByName("Distance"));
|
||||
if (prop) {
|
||||
distance = prop->getValue();
|
||||
}
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
JointType getJointType(App::DocumentObject* joint)
|
||||
{
|
||||
JointType jointType = JointType::Fixed;
|
||||
|
||||
auto* prop = dynamic_cast<App::PropertyEnumeration*>(joint->getPropertyByName("JointType"));
|
||||
if (prop) {
|
||||
jointType = static_cast<JointType>(prop->getValue());
|
||||
}
|
||||
|
||||
return jointType;
|
||||
}
|
||||
|
||||
const char* getElementFromProp(App::DocumentObject* obj, const char* propName)
|
||||
{
|
||||
auto* prop = dynamic_cast<App::PropertyString*>(obj->getPropertyByName(propName));
|
||||
if (!prop) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return prop->getValue();
|
||||
}
|
||||
|
||||
std::string getElementTypeFromProp(App::DocumentObject* obj, const char* propName)
|
||||
{
|
||||
// The prop is going to be something like 'Edge14' or 'Face7'. We need 'Edge' or 'Face'
|
||||
std::string elementType;
|
||||
for (char ch : std::string(getElementFromProp(obj, propName))) {
|
||||
if (std::isalpha(ch)) {
|
||||
elementType += ch;
|
||||
}
|
||||
}
|
||||
return elementType;
|
||||
}
|
||||
|
||||
App::DocumentObject* getLinkObjFromProp(App::DocumentObject* joint,
|
||||
const char* propLinkName)
|
||||
{
|
||||
auto* propObj = dynamic_cast<App::PropertyLink*>(joint->getPropertyByName(propLinkName));
|
||||
if (!propObj) {
|
||||
return nullptr;
|
||||
}
|
||||
return propObj->getValue();
|
||||
}
|
||||
|
||||
App::DocumentObject* getObjFromNameProp(App::DocumentObject* joint,
|
||||
const char* pObjName,
|
||||
const char* pPart)
|
||||
{
|
||||
auto* propObjName = dynamic_cast<App::PropertyString*>(joint->getPropertyByName(pObjName));
|
||||
if (!propObjName) {
|
||||
return nullptr;
|
||||
}
|
||||
std::string objName = std::string(propObjName->getValue());
|
||||
|
||||
App::DocumentObject* containingPart = getLinkObjFromProp(joint, pPart);
|
||||
if (!containingPart) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (objName == containingPart->getNameInDocument()) {
|
||||
return containingPart;
|
||||
}
|
||||
|
||||
if (containingPart->getTypeId().isDerivedFrom(App::Link::getClassTypeId())) {
|
||||
App::Link* link = dynamic_cast<App::Link*>(containingPart);
|
||||
|
||||
containingPart = link->getLinkedObject();
|
||||
if (!containingPart) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto obj : containingPart->getOutList()) {
|
||||
if (objName == obj->getNameInDocument()) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
App::DocumentObject* getLinkedObjFromNameProp(App::DocumentObject* joint,
|
||||
const char* pObjName,
|
||||
const char* pPart)
|
||||
{
|
||||
auto* obj = getObjFromNameProp(joint, pObjName, pPart);
|
||||
if (obj) {
|
||||
return obj->getLinkedObject(true);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace Assembly
|
||||
*/
|
||||
72
src/Mod/Assembly/App/AssemblyUtils.h
Normal file
@@ -0,0 +1,72 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#ifndef ASSEMBLY_AssemblyUtils_H
|
||||
#define ASSEMBLY_AssemblyUtils_H
|
||||
|
||||
|
||||
#include <Mod/Assembly/AssemblyGlobal.h>
|
||||
|
||||
#include <App/FeaturePython.h>
|
||||
#include <App/Part.h>
|
||||
|
||||
namespace App
|
||||
{
|
||||
class DocumentObject;
|
||||
} // namespace App
|
||||
|
||||
namespace Base
|
||||
{
|
||||
class Placement;
|
||||
} // namespace Base
|
||||
|
||||
namespace Assembly
|
||||
{
|
||||
/*
|
||||
// This enum has to be the same as the one in JointObject.py
|
||||
enum class JointType
|
||||
{
|
||||
Fixed,
|
||||
Revolute,
|
||||
Cylindrical,
|
||||
Slider,
|
||||
Ball,
|
||||
Distance
|
||||
};
|
||||
|
||||
// getters to get from properties
|
||||
double getJointDistance(App::DocumentObject* joint);
|
||||
JointType getJointType(App::DocumentObject* joint);
|
||||
const char* getElementFromProp(App::DocumentObject* obj, const char* propName);
|
||||
std::string getElementTypeFromProp(App::DocumentObject* obj, const char* propName);
|
||||
App::DocumentObject* getLinkObjFromProp(App::DocumentObject* joint, const char* propName);
|
||||
App::DocumentObject* getObjFromNameProp(App::DocumentObject* joint, const char* pObjName, const
|
||||
char* pPart); App::DocumentObject* getLinkedObjFromNameProp(App::DocumentObject* joint, const char*
|
||||
pObjName, const char* pPart); Base::Placement getPlacementFromProp(App::DocumentObject* obj, const
|
||||
char* propName);*/
|
||||
|
||||
} // namespace Assembly
|
||||
|
||||
|
||||
#endif // ASSEMBLY_AssemblyUtils_H
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
include_directories(
|
||||
${CMAKE_BINARY_DIR}
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
${CMAKE_SOURCE_DIR}/src/3rdParty/OndselSolver
|
||||
${CMAKE_BINARY_DIR}/src
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${OCC_INCLUDE_DIR}
|
||||
${PYTHON_INCLUDE_DIRS}
|
||||
)
|
||||
link_directories(${OCC_LIBRARY_DIR})
|
||||
|
||||
set(Assembly_LIBS
|
||||
Part
|
||||
PartDesign
|
||||
FreeCADApp
|
||||
OndselSolver
|
||||
)
|
||||
|
||||
generate_from_xml(AssemblyObjectPy)
|
||||
generate_from_xml(JointGroupPy)
|
||||
|
||||
SET(Python_SRCS
|
||||
AssemblyObjectPy.xml
|
||||
AssemblyObjectPyImp.cpp
|
||||
JointGroupPy.xml
|
||||
JointGroupPyImp.cpp
|
||||
)
|
||||
SOURCE_GROUP("Python" FILES ${Python_SRCS})
|
||||
|
||||
SET(Module_SRCS
|
||||
AppAssembly.cpp
|
||||
AppAssemblyPy.cpp
|
||||
PreCompiled.cpp
|
||||
PreCompiled.h
|
||||
)
|
||||
SOURCE_GROUP("Module" FILES ${Module_SRCS})
|
||||
|
||||
SET(Assembly_SRCS
|
||||
AssemblyObject.cpp
|
||||
AssemblyObject.h
|
||||
JointGroup.cpp
|
||||
JointGroup.h
|
||||
${Module_SRCS}
|
||||
${Python_SRCS}
|
||||
)
|
||||
|
||||
add_library(Assembly SHARED ${Assembly_SRCS})
|
||||
target_link_libraries(Assembly ${Assembly_LIBS})
|
||||
|
||||
if(FREECAD_USE_PCH)
|
||||
add_definitions(-D_PreComp_)
|
||||
GET_MSVC_PRECOMPILED_SOURCE("PreCompiled.cpp" PCH_SRCS ${Assembly_SRCS})
|
||||
ADD_MSVC_PRECOMPILED_HEADER(Assembly PreCompiled.h PreCompiled.cpp PCH_SRCS)
|
||||
endif(FREECAD_USE_PCH)
|
||||
|
||||
SET_BIN_DIR(Assembly AssemblyApp /Mod/Assembly)
|
||||
SET_PYTHON_PREFIX_SUFFIX(Assembly)
|
||||
|
||||
INSTALL(TARGETS Assembly DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
|
||||
55
src/Mod/Assembly/App/JointGroup.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
#ifndef _PreComp_
|
||||
#endif
|
||||
|
||||
#include <App/Application.h>
|
||||
#include <App/Document.h>
|
||||
#include <App/FeaturePythonPyImp.h>
|
||||
#include <App/PropertyPythonObject.h>
|
||||
#include <Base/Console.h>
|
||||
#include <Base/Tools.h>
|
||||
|
||||
#include "JointGroup.h"
|
||||
#include "JointGroupPy.h"
|
||||
|
||||
using namespace Assembly;
|
||||
|
||||
|
||||
PROPERTY_SOURCE(Assembly::JointGroup, App::DocumentObjectGroup)
|
||||
|
||||
JointGroup::JointGroup()
|
||||
{}
|
||||
|
||||
JointGroup::~JointGroup() = default;
|
||||
|
||||
PyObject* JointGroup::getPyObject()
|
||||
{
|
||||
if (PythonObject.is(Py::_None())) {
|
||||
// ref counter is set to 1
|
||||
PythonObject = Py::Object(new JointGroupPy(this), true);
|
||||
}
|
||||
return Py::new_reference_to(PythonObject);
|
||||
}
|
||||
58
src/Mod/Assembly/App/JointGroup.h
Normal file
@@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#ifndef ASSEMBLY_JointGroup_H
|
||||
#define ASSEMBLY_JointGroup_H
|
||||
|
||||
#include <Mod/Assembly/AssemblyGlobal.h>
|
||||
|
||||
#include <App/DocumentObjectGroup.h>
|
||||
#include <App/PropertyLinks.h>
|
||||
|
||||
|
||||
namespace Assembly
|
||||
{
|
||||
|
||||
class AssemblyExport JointGroup: public App::DocumentObjectGroup
|
||||
{
|
||||
PROPERTY_HEADER_WITH_OVERRIDE(Assembly::JointGroup);
|
||||
|
||||
public:
|
||||
JointGroup();
|
||||
~JointGroup() override;
|
||||
|
||||
PyObject* getPyObject() override;
|
||||
|
||||
/// returns the type name of the ViewProvider
|
||||
const char* getViewProviderName() const override
|
||||
{
|
||||
return "AssemblyGui::ViewProviderJointGroup";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace Assembly
|
||||
|
||||
|
||||
#endif // ASSEMBLY_JointGroup_H
|
||||
19
src/Mod/Assembly/App/JointGroupPy.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<GenerateModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="generateMetaModel_Module.xsd">
|
||||
<PythonExport
|
||||
Father="DocumentObjectGroupPy"
|
||||
Name="JointGroupPy"
|
||||
Twin="JointGroup"
|
||||
TwinPointer="JointGroup"
|
||||
Include="Mod/Assembly/App/JointGroup.h"
|
||||
Namespace="Assembly"
|
||||
FatherInclude="App/DocumentObjectGroupPy.h"
|
||||
FatherNamespace="App">
|
||||
<Documentation>
|
||||
<Author Licence="LGPL" Name="Ondsel" EMail="development@ondsel.com" />
|
||||
<UserDocu>This class is a group subclass for joints.</UserDocu>
|
||||
</Documentation>
|
||||
|
||||
<CustomAttributes />
|
||||
</PythonExport>
|
||||
</GenerateModel>
|
||||
46
src/Mod/Assembly/App/JointGroupPyImp.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2014 Jürgen Riegel <juergen.riegel@web.de> *
|
||||
* *
|
||||
* 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"
|
||||
|
||||
// inclusion of the generated files (generated out of JointGroup.xml)
|
||||
#include "JointGroupPy.h"
|
||||
#include "JointGroupPy.cpp"
|
||||
|
||||
using namespace Assembly;
|
||||
|
||||
// returns a string which represents the object e.g. when printed in python
|
||||
std::string JointGroupPy::representation() const
|
||||
{
|
||||
return {"<Joint Group>"};
|
||||
}
|
||||
|
||||
PyObject* JointGroupPy::getCustomAttributes(const char* /*attr*/) const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int JointGroupPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
25
src/Mod/Assembly/App/PreCompiled.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#include "PreCompiled.h"
|
||||
51
src/Mod/Assembly/App/PreCompiled.h
Normal file
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef ASSEMBLY_PRECOMPILED_H
|
||||
#define ASSEMBLY_PRECOMPILED_H
|
||||
|
||||
#include <FCConfig.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 5208)
|
||||
#endif
|
||||
|
||||
#ifdef _PreComp_
|
||||
|
||||
// standard
|
||||
#include <cinttypes>
|
||||
#include <cmath>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <BRepAdaptor_Curve.hxx>
|
||||
#include <BRepAdaptor_Surface.hxx>
|
||||
#include <TopoDS.hxx>
|
||||
#include <TopoDS_Face.hxx>
|
||||
|
||||
#endif // _PreComp_
|
||||
#endif // ASSEMBLY_PRECOMPILED_H
|
||||
@@ -0,0 +1 @@
|
||||
import AssemblyApp
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,7 +19,7 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
|
||||
def open(filename):
|
||||
|
||||
@@ -21,10 +21,18 @@
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
|
||||
import FreeCAD
|
||||
import FreeCAD as App
|
||||
import Part
|
||||
import unittest
|
||||
|
||||
import UtilsAssembly
|
||||
import JointObject
|
||||
|
||||
|
||||
def _msg(text, end="\n"):
|
||||
"""Write messages to the console including the line ending."""
|
||||
App.Console.PrintMessage(text + end)
|
||||
|
||||
|
||||
class TestCore(unittest.TestCase):
|
||||
@classmethod
|
||||
@@ -49,26 +57,167 @@ class TestCore(unittest.TestCase):
|
||||
"""
|
||||
pass
|
||||
|
||||
# Close geometry document without saving
|
||||
# FreeCAD.closeDocument(FreeCAD.ActiveDocument.Name)
|
||||
|
||||
# Setup and tear down methods called before and after each unit test
|
||||
def setUp(self):
|
||||
"""setUp()...
|
||||
This method is called prior to each `test()` method. Add code and objects here
|
||||
that are needed for multiple `test()` methods.
|
||||
"""
|
||||
self.doc = FreeCAD.ActiveDocument
|
||||
self.con = FreeCAD.Console
|
||||
doc_name = self.__class__.__name__
|
||||
if App.ActiveDocument:
|
||||
if App.ActiveDocument.Name != doc_name:
|
||||
App.newDocument(doc_name)
|
||||
else:
|
||||
App.newDocument(doc_name)
|
||||
App.setActiveDocument(doc_name)
|
||||
self.doc = App.ActiveDocument
|
||||
|
||||
self.assembly = App.ActiveDocument.addObject("Assembly::AssemblyObject", "Assembly")
|
||||
if self.assembly:
|
||||
self.jointgroup = self.assembly.newObject("Assembly::JointGroup", "Joints")
|
||||
|
||||
_msg(" Temporary document '{}'".format(self.doc.Name))
|
||||
|
||||
def tearDown(self):
|
||||
"""tearDown()...
|
||||
This method is called after each test() method. Add cleanup instructions here.
|
||||
Such cleanup instructions will likely undo those in the setUp() method.
|
||||
"""
|
||||
pass
|
||||
App.closeDocument(self.doc.Name)
|
||||
|
||||
def test00(self):
|
||||
pass
|
||||
def test_create_assembly(self):
|
||||
"""Create an assembly."""
|
||||
operation = "Create Assembly Object"
|
||||
_msg(" Test '{}'".format(operation))
|
||||
self.assertTrue(self.assembly, "'{}' failed".format(operation))
|
||||
|
||||
self.assertTrue(True)
|
||||
def test_create_jointGroup(self):
|
||||
"""Create a joint group in an assembly."""
|
||||
operation = "Create JointGroup Object"
|
||||
_msg(" Test '{}'".format(operation))
|
||||
self.assertTrue(self.jointgroup, "'{}' failed".format(operation))
|
||||
|
||||
def test_create_joint(self):
|
||||
"""Create a joint in an assembly."""
|
||||
operation = "Create Joint Object"
|
||||
_msg(" Test '{}'".format(operation))
|
||||
|
||||
joint = self.jointgroup.newObject("App::FeaturePython", "testJoint")
|
||||
self.assertTrue(joint, "'{}' failed (FeaturePython creation failed)".format(operation))
|
||||
JointObject.Joint(joint, 0)
|
||||
|
||||
self.assertTrue(hasattr(joint, "JointType"), "'{}' failed".format(operation))
|
||||
|
||||
def test_create_grounded_joint(self):
|
||||
"""Create a grounded joint in an assembly."""
|
||||
operation = "Create Grounded Joint Object"
|
||||
_msg(" Test '{}'".format(operation))
|
||||
|
||||
groundedjoint = self.jointgroup.newObject("App::FeaturePython", "testJoint")
|
||||
self.assertTrue(
|
||||
groundedjoint, "'{}' failed (FeaturePython creation failed)".format(operation)
|
||||
)
|
||||
|
||||
box = self.assembly.newObject("Part::Box", "Box")
|
||||
|
||||
JointObject.GroundedJoint(groundedjoint, box)
|
||||
|
||||
self.assertTrue(
|
||||
hasattr(groundedjoint, "ObjectToGround"),
|
||||
"'{}' failed: No attribute 'ObjectToGround'".format(operation),
|
||||
)
|
||||
self.assertTrue(
|
||||
groundedjoint.ObjectToGround == box,
|
||||
"'{}' failed: ObjectToGround not set correctly.".format(operation),
|
||||
)
|
||||
|
||||
def test_find_placement(self):
|
||||
"""Test find placement of joint."""
|
||||
operation = "Find placement"
|
||||
_msg(" Test '{}'".format(operation))
|
||||
|
||||
joint = self.jointgroup.newObject("App::FeaturePython", "testJoint")
|
||||
JointObject.Joint(joint, 0)
|
||||
|
||||
L = 2
|
||||
W = 3
|
||||
H = 7
|
||||
box = self.assembly.newObject("Part::Box", "Box")
|
||||
box.Length = L
|
||||
box.Width = W
|
||||
box.Height = H
|
||||
box.Placement = App.Placement(App.Vector(10, 20, 30), App.Rotation(15, 25, 35))
|
||||
|
||||
# Step 0 : box with placement. No element selected
|
||||
plc = joint.Proxy.findPlacement(joint, box.Name, box, "", "")
|
||||
targetPlc = App.Placement(App.Vector(), App.Rotation())
|
||||
self.assertTrue(plc.isSame(targetPlc, 1e-6), "'{}' failed - Step 0".format(operation))
|
||||
|
||||
# Step 1 : box with placement. Face + Vertex
|
||||
plc = joint.Proxy.findPlacement(joint, box.Name, box, "Face6", "Vertex7")
|
||||
targetPlc = App.Placement(App.Vector(L, W, H), App.Rotation())
|
||||
self.assertTrue(plc.isSame(targetPlc, 1e-6), "'{}' failed - Step 1".format(operation))
|
||||
|
||||
# Step 2 : box with placement. Edge + Vertex
|
||||
plc = joint.Proxy.findPlacement(joint, box.Name, box, "Edge8", "Vertex8")
|
||||
targetPlc = App.Placement(App.Vector(L, W, 0), App.Rotation(0, 0, -90))
|
||||
self.assertTrue(plc.isSame(targetPlc, 1e-6), "'{}' failed - Step 2".format(operation))
|
||||
|
||||
# Step 3 : box with placement. Vertex
|
||||
plc = joint.Proxy.findPlacement(joint, box.Name, box, "Vertex3", "Vertex3")
|
||||
targetPlc = App.Placement(App.Vector(0, W, H), App.Rotation())
|
||||
_msg(" plc '{}'".format(plc))
|
||||
_msg(" targetPlc '{}'".format(targetPlc))
|
||||
self.assertTrue(plc.isSame(targetPlc, 1e-6), "'{}' failed - Step 3".format(operation))
|
||||
|
||||
# Step 4 : box with placement. Face
|
||||
plc = joint.Proxy.findPlacement(joint, box.Name, box, "Face2", "Face2")
|
||||
targetPlc = App.Placement(App.Vector(L, W / 2, H / 2), App.Rotation(0, -90, 180))
|
||||
_msg(" plc '{}'".format(plc))
|
||||
_msg(" targetPlc '{}'".format(targetPlc))
|
||||
self.assertTrue(plc.isSame(targetPlc, 1e-6), "'{}' failed - Step 4".format(operation))
|
||||
|
||||
def test_solve_assembly(self):
|
||||
"""Test solving an assembly."""
|
||||
operation = "Solve assembly"
|
||||
_msg(" Test '{}'".format(operation))
|
||||
|
||||
box = self.assembly.newObject("Part::Box", "Box")
|
||||
box.Length = 10
|
||||
box.Width = 10
|
||||
box.Height = 10
|
||||
box.Placement = App.Placement(App.Vector(10, 20, 30), App.Rotation(15, 25, 35))
|
||||
|
||||
box2 = self.assembly.newObject("Part::Box", "Box")
|
||||
box2.Length = 10
|
||||
box2.Width = 10
|
||||
box2.Height = 10
|
||||
box2.Placement = App.Placement(App.Vector(40, 50, 60), App.Rotation(45, 55, 65))
|
||||
|
||||
ground = self.jointgroup.newObject("App::FeaturePython", "GroundedJoint")
|
||||
JointObject.GroundedJoint(ground, box2)
|
||||
|
||||
joint = self.jointgroup.newObject("App::FeaturePython", "testJoint")
|
||||
JointObject.Joint(joint, 0)
|
||||
|
||||
current_selection = []
|
||||
current_selection.append(
|
||||
{
|
||||
"object": box2,
|
||||
"part": box2,
|
||||
"element_name": "Face6",
|
||||
"vertex_name": "Vertex7",
|
||||
}
|
||||
)
|
||||
current_selection.append(
|
||||
{
|
||||
"object": box,
|
||||
"part": box,
|
||||
"element_name": "Face6",
|
||||
"vertex_name": "Vertex7",
|
||||
}
|
||||
)
|
||||
|
||||
joint.Proxy.setJointConnectors(joint, current_selection)
|
||||
|
||||
self.assertTrue(box.Placement.isSame(box2.Placement, 1e-6), "'{}'".format(operation))
|
||||
|
||||
@@ -8,7 +8,9 @@ set(Assembly_Scripts
|
||||
Init.py
|
||||
CommandCreateAssembly.py
|
||||
CommandInsertLink.py
|
||||
CommandSolveAssembly.py
|
||||
CommandCreateJoint.py
|
||||
CommandExportASMT.py
|
||||
TestAssemblyWorkbench.py
|
||||
JointObject.py
|
||||
Preferences.py
|
||||
@@ -65,3 +67,9 @@ INSTALL(
|
||||
DESTINATION
|
||||
Mod/Assembly/AssemblyTests
|
||||
)
|
||||
INSTALL(
|
||||
FILES
|
||||
${AssemblyScripts_SRCS}
|
||||
DESTINATION
|
||||
Mod/Assembly/Assembly
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,7 +19,7 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
import FreeCAD as App
|
||||
|
||||
@@ -28,6 +28,9 @@ from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
if App.GuiUp:
|
||||
import FreeCADGui as Gui
|
||||
|
||||
import UtilsAssembly
|
||||
import Preferences
|
||||
|
||||
# translate = App.Qt.translate
|
||||
|
||||
__title__ = "Assembly Command Create Assembly"
|
||||
@@ -46,20 +49,33 @@ class CommandCreateAssembly:
|
||||
"Accel": "A",
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"Assembly_CreateAssembly",
|
||||
"Create an assembly object in the current document.",
|
||||
"Create an assembly object in the current document, or in the current active assembly (if any). Limit of one root assembly per file.",
|
||||
),
|
||||
"CmdType": "ForEdit",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
if Preferences.preferences().GetBool("EnforceOneAssemblyRule", True):
|
||||
activeAssembly = UtilsAssembly.activeAssembly()
|
||||
|
||||
if UtilsAssembly.isThereOneRootAssembly() and not activeAssembly:
|
||||
return False
|
||||
|
||||
return App.ActiveDocument is not None
|
||||
|
||||
def Activated(self):
|
||||
App.setActiveTransaction("Create assembly")
|
||||
assembly = App.ActiveDocument.addObject("App::Part", "Assembly")
|
||||
|
||||
activeAssembly = UtilsAssembly.activeAssembly()
|
||||
if activeAssembly:
|
||||
assembly = activeAssembly.newObject("Assembly::AssemblyObject", "Assembly")
|
||||
else:
|
||||
assembly = App.ActiveDocument.addObject("Assembly::AssemblyObject", "Assembly")
|
||||
|
||||
assembly.Type = "Assembly"
|
||||
Gui.ActiveDocument.ActiveView.setActiveObject("part", assembly)
|
||||
assembly.newObject("App::DocumentObjectGroup", "Joints")
|
||||
if not activeAssembly:
|
||||
Gui.ActiveDocument.setEdit(assembly)
|
||||
assembly.newObject("Assembly::JointGroup", "Joints")
|
||||
App.closeActiveTransaction()
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,7 +19,7 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
import os
|
||||
import FreeCAD as App
|
||||
@@ -31,6 +31,7 @@ if App.GuiUp:
|
||||
from PySide import QtCore, QtGui, QtWidgets
|
||||
|
||||
import JointObject
|
||||
from JointObject import TaskAssemblyCreateJoint
|
||||
import UtilsAssembly
|
||||
import Assembly_rc
|
||||
|
||||
@@ -41,6 +42,18 @@ __author__ = "Ondsel"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
|
||||
def isCreateJointActive():
|
||||
return UtilsAssembly.isAssemblyGrounded() and UtilsAssembly.assembly_has_at_least_n_parts(2)
|
||||
|
||||
|
||||
def activateJoint(index):
|
||||
if JointObject.activeTask:
|
||||
JointObject.activeTask.reject()
|
||||
|
||||
panel = TaskAssemblyCreateJoint(index)
|
||||
Gui.Control.showDialog(panel)
|
||||
|
||||
|
||||
class CommandCreateJointFixed:
|
||||
def __init__(self):
|
||||
pass
|
||||
@@ -49,26 +62,34 @@ class CommandCreateJointFixed:
|
||||
|
||||
return {
|
||||
"Pixmap": "Assembly_CreateJointFixed",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointFixed", "Create Fixed Joint"),
|
||||
"Accel": "F",
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"MenuText": QT_TRANSLATE_NOOP(
|
||||
"Assembly_CreateJointFixed",
|
||||
"<p>Create a Fixed Joint: Permanently locks two parts together, preventing any movement or rotation.</p>",
|
||||
"Create a Fixed Joint",
|
||||
),
|
||||
"Accel": "J",
|
||||
"ToolTip": "<p>"
|
||||
+ QT_TRANSLATE_NOOP(
|
||||
"Assembly_CreateJointFixed",
|
||||
"1 - If an assembly is active : Create a joint permanently locking two parts together, preventing any movement or rotation.",
|
||||
)
|
||||
+ "</p>"
|
||||
+ "<p>"
|
||||
+ QT_TRANSLATE_NOOP(
|
||||
"Assembly_CreateJointFixed",
|
||||
"2 - If a part is active : Position sub parts by matching selected coordinate systems. The second part selected will move.",
|
||||
)
|
||||
+ "</p>",
|
||||
"CmdType": "ForEdit",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
return UtilsAssembly.activeAssembly() is not None
|
||||
if UtilsAssembly.activePart:
|
||||
return UtilsAssembly.assembly_has_at_least_n_parts(2)
|
||||
|
||||
return UtilsAssembly.isAssemblyGrounded() and UtilsAssembly.assembly_has_at_least_n_parts(2)
|
||||
|
||||
def Activated(self):
|
||||
assembly = UtilsAssembly.activeAssembly()
|
||||
if not assembly:
|
||||
return
|
||||
view = Gui.activeDocument().activeView()
|
||||
|
||||
self.panel = TaskAssemblyCreateJoint(assembly, view, 0)
|
||||
Gui.Control.showDialog(self.panel)
|
||||
activateJoint(0)
|
||||
|
||||
|
||||
class CommandCreateJointRevolute:
|
||||
@@ -81,24 +102,20 @@ class CommandCreateJointRevolute:
|
||||
"Pixmap": "Assembly_CreateJointRevolute",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointRevolute", "Create Revolute Joint"),
|
||||
"Accel": "R",
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"ToolTip": "<p>"
|
||||
+ QT_TRANSLATE_NOOP(
|
||||
"Assembly_CreateJointRevolute",
|
||||
"<p>Create a Revolute Joint: Allows rotation around a single axis between selected parts.</p>",
|
||||
),
|
||||
"Create a Revolute Joint: Allows rotation around a single axis between selected parts.",
|
||||
)
|
||||
+ "</p>",
|
||||
"CmdType": "ForEdit",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
return UtilsAssembly.activeAssembly() is not None
|
||||
return isCreateJointActive()
|
||||
|
||||
def Activated(self):
|
||||
assembly = UtilsAssembly.activeAssembly()
|
||||
if not assembly:
|
||||
return
|
||||
view = Gui.activeDocument().activeView()
|
||||
|
||||
self.panel = TaskAssemblyCreateJoint(assembly, view, 1)
|
||||
Gui.Control.showDialog(self.panel)
|
||||
activateJoint(1)
|
||||
|
||||
|
||||
class CommandCreateJointCylindrical:
|
||||
@@ -113,24 +130,20 @@ class CommandCreateJointCylindrical:
|
||||
"Assembly_CreateJointCylindrical", "Create Cylindrical Joint"
|
||||
),
|
||||
"Accel": "C",
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"ToolTip": "<p>"
|
||||
+ QT_TRANSLATE_NOOP(
|
||||
"Assembly_CreateJointCylindrical",
|
||||
"<p>Create a Cylindrical Joint: Enables rotation along one axis while permitting movement along the same axis between assembled parts.</p>",
|
||||
),
|
||||
"Create a Cylindrical Joint: Enables rotation along one axis while permitting movement along the same axis between assembled parts.",
|
||||
)
|
||||
+ "</p>",
|
||||
"CmdType": "ForEdit",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
return UtilsAssembly.activeAssembly() is not None
|
||||
return isCreateJointActive()
|
||||
|
||||
def Activated(self):
|
||||
assembly = UtilsAssembly.activeAssembly()
|
||||
if not assembly:
|
||||
return
|
||||
view = Gui.activeDocument().activeView()
|
||||
|
||||
self.panel = TaskAssemblyCreateJoint(assembly, view, 2)
|
||||
Gui.Control.showDialog(self.panel)
|
||||
activateJoint(2)
|
||||
|
||||
|
||||
class CommandCreateJointSlider:
|
||||
@@ -143,24 +156,20 @@ class CommandCreateJointSlider:
|
||||
"Pixmap": "Assembly_CreateJointSlider",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointSlider", "Create Slider Joint"),
|
||||
"Accel": "S",
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"ToolTip": "<p>"
|
||||
+ QT_TRANSLATE_NOOP(
|
||||
"Assembly_CreateJointSlider",
|
||||
"<p>Create a Slider Joint: Allows linear movement along a single axis but restricts rotation between selected parts.</p>",
|
||||
),
|
||||
"Create a Slider Joint: Allows linear movement along a single axis but restricts rotation between selected parts.",
|
||||
)
|
||||
+ "</p>",
|
||||
"CmdType": "ForEdit",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
return UtilsAssembly.activeAssembly() is not None
|
||||
return isCreateJointActive()
|
||||
|
||||
def Activated(self):
|
||||
assembly = UtilsAssembly.activeAssembly()
|
||||
if not assembly:
|
||||
return
|
||||
view = Gui.activeDocument().activeView()
|
||||
|
||||
self.panel = TaskAssemblyCreateJoint(assembly, view, 3)
|
||||
Gui.Control.showDialog(self.panel)
|
||||
activateJoint(3)
|
||||
|
||||
|
||||
class CommandCreateJointBall:
|
||||
@@ -173,333 +182,141 @@ class CommandCreateJointBall:
|
||||
"Pixmap": "Assembly_CreateJointBall",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointBall", "Create Ball Joint"),
|
||||
"Accel": "B",
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"ToolTip": "<p>"
|
||||
+ QT_TRANSLATE_NOOP(
|
||||
"Assembly_CreateJointBall",
|
||||
"<p>Create a Ball Joint: Connects parts at a point, allowing unrestricted movement as long as the connection points remain in contact.</p>",
|
||||
),
|
||||
"Create a Ball Joint: Connects parts at a point, allowing unrestricted movement as long as the connection points remain in contact.",
|
||||
)
|
||||
+ "</p>",
|
||||
"CmdType": "ForEdit",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
return UtilsAssembly.activeAssembly() is not None
|
||||
return isCreateJointActive()
|
||||
|
||||
def Activated(self):
|
||||
assembly = UtilsAssembly.activeAssembly()
|
||||
if not assembly:
|
||||
return
|
||||
view = Gui.activeDocument().activeView()
|
||||
|
||||
self.panel = TaskAssemblyCreateJoint(assembly, view, 4)
|
||||
Gui.Control.showDialog(self.panel)
|
||||
activateJoint(4)
|
||||
|
||||
|
||||
class CommandCreateJointPlanar:
|
||||
class CommandCreateJointDistance:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def GetResources(self):
|
||||
|
||||
return {
|
||||
"Pixmap": "Assembly_CreateJointPlanar",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointPlanar", "Create Planar Joint"),
|
||||
"Accel": "P",
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"Assembly_CreateJointPlanar",
|
||||
"<p>Create a Planar Joint: Ensures two selected features are in the same plane, restricting movement to that plane.</p>",
|
||||
),
|
||||
"Pixmap": "Assembly_CreateJointDistance",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointDistance", "Create Distance Joint"),
|
||||
"Accel": "D",
|
||||
"ToolTip": "<p>"
|
||||
+ QT_TRANSLATE_NOOP(
|
||||
"Assembly_CreateJointDistance",
|
||||
"Create a Distance Joint: Fix the distance between the selected objects.",
|
||||
)
|
||||
+ "</p>",
|
||||
"CmdType": "ForEdit",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
return UtilsAssembly.activeAssembly() is not None
|
||||
# return False
|
||||
return isCreateJointActive()
|
||||
|
||||
def Activated(self):
|
||||
assembly = UtilsAssembly.activeAssembly()
|
||||
if not assembly:
|
||||
return
|
||||
view = Gui.activeDocument().activeView()
|
||||
|
||||
self.panel = TaskAssemblyCreateJoint(assembly, view, 5)
|
||||
Gui.Control.showDialog(self.panel)
|
||||
activateJoint(5)
|
||||
|
||||
|
||||
class CommandCreateJointParallel:
|
||||
def createGroundedJoint(obj):
|
||||
assembly = UtilsAssembly.activeAssembly()
|
||||
if not assembly:
|
||||
return
|
||||
|
||||
joint_group = UtilsAssembly.getJointGroup(assembly)
|
||||
|
||||
obj.Label = obj.Label + " 🔒"
|
||||
ground = joint_group.newObject("App::FeaturePython", "GroundedJoint")
|
||||
JointObject.GroundedJoint(ground, obj)
|
||||
JointObject.ViewProviderGroundedJoint(ground.ViewObject)
|
||||
return ground
|
||||
|
||||
|
||||
class CommandToggleGrounded:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def GetResources(self):
|
||||
|
||||
return {
|
||||
"Pixmap": "Assembly_CreateJointParallel",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointParallel", "Create Parallel Joint"),
|
||||
"Accel": "L",
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"Assembly_CreateJointParallel",
|
||||
"<p>Create a Parallel Joint: Aligns two features to be parallel, constraining relative movement to parallel translations.</p>",
|
||||
),
|
||||
"Pixmap": "Assembly_ToggleGrounded",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Assembly_ToggleGrounded", "Toggle grounded"),
|
||||
"Accel": "G",
|
||||
"ToolTip": "<p>"
|
||||
+ QT_TRANSLATE_NOOP(
|
||||
"Assembly_ToggleGrounded",
|
||||
"Grounding a part permanently locks its position in the assembly, preventing any movement or rotation. You need at least one grounded part before starting to assemble.",
|
||||
)
|
||||
+ "</p>",
|
||||
"CmdType": "ForEdit",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
return UtilsAssembly.activeAssembly() is not None
|
||||
|
||||
def Activated(self):
|
||||
assembly = UtilsAssembly.activeAssembly()
|
||||
if not assembly:
|
||||
return
|
||||
view = Gui.activeDocument().activeView()
|
||||
|
||||
self.panel = TaskAssemblyCreateJoint(assembly, view, 6)
|
||||
Gui.Control.showDialog(self.panel)
|
||||
|
||||
|
||||
class CommandCreateJointTangent:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def GetResources(self):
|
||||
|
||||
return {
|
||||
"Pixmap": "Assembly_CreateJointTangent",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointTangent", "Create Tangent Joint"),
|
||||
"Accel": "T",
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"Assembly_CreateJointTangent",
|
||||
"<p>Create a Tangent Joint: Forces two features to be tangent, restricting movement to smooth transitions along their contact surface.</p>",
|
||||
),
|
||||
"CmdType": "ForEdit",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
return UtilsAssembly.activeAssembly() is not None
|
||||
|
||||
def Activated(self):
|
||||
assembly = UtilsAssembly.activeAssembly()
|
||||
if not assembly:
|
||||
return
|
||||
view = Gui.activeDocument().activeView()
|
||||
|
||||
self.panel = TaskAssemblyCreateJoint(assembly, view, 7)
|
||||
Gui.Control.showDialog(self.panel)
|
||||
|
||||
|
||||
class MakeJointSelGate:
|
||||
def __init__(self, taskbox, assembly):
|
||||
self.taskbox = taskbox
|
||||
self.assembly = assembly
|
||||
|
||||
def allow(self, doc, obj, sub):
|
||||
if not sub:
|
||||
return False
|
||||
|
||||
objs_names, element_name = UtilsAssembly.getObjsNamesAndElement(obj.Name, sub)
|
||||
|
||||
if self.assembly.Name not in objs_names or element_name == "":
|
||||
# Only objects within the assembly. And not whole objects, only elements.
|
||||
return False
|
||||
|
||||
if Gui.Selection.isSelected(obj, sub, Gui.Selection.ResolveMode.NoResolve):
|
||||
# If it's to deselect then it's ok
|
||||
return True
|
||||
|
||||
if len(self.taskbox.current_selection) >= 2:
|
||||
# No more than 2 elements can be selected for basic joints.
|
||||
return False
|
||||
|
||||
full_obj_name = ".".join(objs_names)
|
||||
for selection_dict in self.taskbox.current_selection:
|
||||
if selection_dict["full_obj_name"] == full_obj_name:
|
||||
# Can't join a solid to itself. So the user need to select 2 different parts.
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class TaskAssemblyCreateJoint(QtCore.QObject):
|
||||
def __init__(self, assembly, view, jointTypeIndex):
|
||||
super().__init__()
|
||||
|
||||
self.assembly = assembly
|
||||
self.view = view
|
||||
self.doc = App.ActiveDocument
|
||||
|
||||
self.form = Gui.PySideUic.loadUi(":/panels/TaskAssemblyCreateJoint.ui")
|
||||
|
||||
self.form.jointType.addItems(JointObject.JointTypes)
|
||||
self.form.jointType.setCurrentIndex(jointTypeIndex)
|
||||
|
||||
Gui.Selection.clearSelection()
|
||||
Gui.Selection.addSelectionGate(
|
||||
MakeJointSelGate(self, self.assembly), Gui.Selection.ResolveMode.NoResolve
|
||||
return (
|
||||
UtilsAssembly.isAssemblyCommandActive()
|
||||
and UtilsAssembly.assembly_has_at_least_n_parts(1)
|
||||
)
|
||||
Gui.Selection.addObserver(self, Gui.Selection.ResolveMode.NoResolve)
|
||||
Gui.Selection.setSelectionStyle(Gui.Selection.SelectionStyle.GreedySelection)
|
||||
self.current_selection = []
|
||||
self.preselection_dict = None
|
||||
|
||||
self.callbackMove = self.view.addEventCallback("SoLocation2Event", self.moveMouse)
|
||||
self.callbackKey = self.view.addEventCallback("SoKeyboardEvent", self.KeyboardEvent)
|
||||
def Activated(self):
|
||||
assembly = UtilsAssembly.activeAssembly()
|
||||
if not assembly:
|
||||
return
|
||||
|
||||
App.setActiveTransaction("Create joint")
|
||||
self.createJointObject()
|
||||
joint_group = UtilsAssembly.getJointGroup(assembly)
|
||||
|
||||
def accept(self):
|
||||
if len(self.current_selection) != 2:
|
||||
App.Console.PrintWarning("You need to select 2 elements from 2 separate parts.")
|
||||
return False
|
||||
self.deactivate()
|
||||
selection = Gui.Selection.getSelectionEx("*", 0)
|
||||
if not selection:
|
||||
return
|
||||
|
||||
App.setActiveTransaction("Toggle grounded")
|
||||
for sel in selection:
|
||||
# If you select 2 solids (bodies for example) within an assembly.
|
||||
# There'll be a single sel but 2 SubElementNames.
|
||||
for sub in sel.SubElementNames:
|
||||
# Only objects within the assembly.
|
||||
objs_names, element_name = UtilsAssembly.getObjsNamesAndElement(sel.ObjectName, sub)
|
||||
if assembly.Name not in objs_names:
|
||||
continue
|
||||
|
||||
full_element_name = UtilsAssembly.getFullElementName(sel.ObjectName, sub)
|
||||
obj = UtilsAssembly.getObject(full_element_name)
|
||||
part_containing_obj = UtilsAssembly.getContainingPart(full_element_name, obj)
|
||||
|
||||
# Check if part is grounded and if so delete the joint.
|
||||
ungrounded = False
|
||||
for joint in joint_group.Group:
|
||||
if (
|
||||
hasattr(joint, "ObjectToGround")
|
||||
and joint.ObjectToGround == part_containing_obj
|
||||
):
|
||||
# Remove grounded tag.
|
||||
if part_containing_obj.Label.endswith(" 🔒"):
|
||||
part_containing_obj.Label = part_containing_obj.Label[:-2]
|
||||
doc = App.ActiveDocument
|
||||
doc.removeObject(joint.Name)
|
||||
doc.recompute()
|
||||
ungrounded = True
|
||||
break
|
||||
if ungrounded:
|
||||
continue
|
||||
|
||||
# Create groundedJoint.
|
||||
createGroundedJoint(part_containing_obj)
|
||||
App.closeActiveTransaction()
|
||||
return True
|
||||
|
||||
def reject(self):
|
||||
self.deactivate()
|
||||
App.closeActiveTransaction(True)
|
||||
return True
|
||||
|
||||
def deactivate(self):
|
||||
Gui.Selection.removeSelectionGate()
|
||||
Gui.Selection.removeObserver(self)
|
||||
Gui.Selection.setSelectionStyle(Gui.Selection.SelectionStyle.NormalSelection)
|
||||
Gui.Selection.clearSelection()
|
||||
self.view.removeEventCallback("SoLocation2Event", self.callbackMove)
|
||||
self.view.removeEventCallback("SoKeyboardEvent", self.callbackKey)
|
||||
if Gui.Control.activeDialog():
|
||||
Gui.Control.closeDialog()
|
||||
|
||||
def createJointObject(self):
|
||||
type_index = self.form.jointType.currentIndex()
|
||||
|
||||
joint_group = self.assembly.getObject("Joints")
|
||||
|
||||
if not joint_group:
|
||||
joint_group = self.assembly.newObject("App::DocumentObjectGroup", "Joints")
|
||||
|
||||
self.joint = joint_group.newObject("App::FeaturePython", "Joint")
|
||||
JointObject.Joint(self.joint, type_index)
|
||||
JointObject.ViewProviderJoint(self.joint.ViewObject, self.joint)
|
||||
|
||||
def updateJoint(self):
|
||||
# First we build the listwidget
|
||||
self.form.featureList.clear()
|
||||
simplified_names = []
|
||||
for sel in self.current_selection:
|
||||
# TODO: ideally we probably want to hide the feature name in case of PartDesign bodies. ie body.face12 and not body.pad2.face12
|
||||
sname = sel["full_element_name"].split(self.assembly.Name + ".", 1)[-1]
|
||||
simplified_names.append(sname)
|
||||
self.form.featureList.addItems(simplified_names)
|
||||
|
||||
# Then we pass the new list to the join object
|
||||
self.joint.Proxy.setJointConnectors(self.current_selection)
|
||||
|
||||
def moveMouse(self, info):
|
||||
if len(self.current_selection) >= 2 or (
|
||||
len(self.current_selection) == 1
|
||||
and self.current_selection[0]["full_element_name"]
|
||||
== self.preselection_dict["full_element_name"]
|
||||
):
|
||||
self.joint.ViewObject.Proxy.showPreviewJCS(False)
|
||||
return
|
||||
|
||||
cursor_pos = self.view.getCursorPos()
|
||||
cursor_info = self.view.getObjectInfo(cursor_pos)
|
||||
# cursor_info example {'x': 41.515, 'y': 7.449, 'z': 16.861, 'ParentObject': <Part object>, 'SubName': 'Body002.Pad.Face5', 'Document': 'part3', 'Object': 'Pad', 'Component': 'Face5'}
|
||||
|
||||
if (
|
||||
not cursor_info
|
||||
or not self.preselection_dict
|
||||
or cursor_info["SubName"] != self.preselection_dict["sub_name"]
|
||||
):
|
||||
self.joint.ViewObject.Proxy.showPreviewJCS(False)
|
||||
return
|
||||
|
||||
# newPos = self.view.getPoint(*info["Position"]) # This is not what we want, it's not pos on the object but on the focal plane
|
||||
|
||||
newPos = App.Vector(cursor_info["x"], cursor_info["y"], cursor_info["z"])
|
||||
self.preselection_dict["mouse_pos"] = newPos
|
||||
|
||||
self.preselection_dict["vertex_name"] = UtilsAssembly.findElementClosestVertex(
|
||||
self.preselection_dict
|
||||
)
|
||||
|
||||
placement = self.joint.Proxy.findPlacement(
|
||||
self.preselection_dict["object"],
|
||||
self.preselection_dict["element_name"],
|
||||
self.preselection_dict["vertex_name"],
|
||||
)
|
||||
self.joint.ViewObject.Proxy.showPreviewJCS(True, placement)
|
||||
self.previewJCSVisible = True
|
||||
|
||||
# 3D view keyboard handler
|
||||
def KeyboardEvent(self, info):
|
||||
if info["State"] == "UP" and info["Key"] == "ESCAPE":
|
||||
self.reject()
|
||||
|
||||
if info["State"] == "UP" and info["Key"] == "RETURN":
|
||||
self.accept()
|
||||
|
||||
# selectionObserver stuff
|
||||
def addSelection(self, doc_name, obj_name, sub_name, mousePos):
|
||||
full_obj_name = UtilsAssembly.getFullObjName(obj_name, sub_name)
|
||||
full_element_name = UtilsAssembly.getFullElementName(obj_name, sub_name)
|
||||
selected_object = UtilsAssembly.getObject(full_element_name)
|
||||
element_name = UtilsAssembly.getElementName(full_element_name)
|
||||
|
||||
selection_dict = {
|
||||
"object": selected_object,
|
||||
"element_name": element_name,
|
||||
"full_element_name": full_element_name,
|
||||
"full_obj_name": full_obj_name,
|
||||
"mouse_pos": App.Vector(mousePos[0], mousePos[1], mousePos[2]),
|
||||
}
|
||||
selection_dict["vertex_name"] = UtilsAssembly.findElementClosestVertex(selection_dict)
|
||||
|
||||
self.current_selection.append(selection_dict)
|
||||
self.updateJoint()
|
||||
|
||||
def removeSelection(self, doc_name, obj_name, sub_name, mousePos=None):
|
||||
full_element_name = UtilsAssembly.getFullElementName(obj_name, sub_name)
|
||||
|
||||
# Find and remove the corresponding dictionary from the combined list
|
||||
selection_dict_to_remove = None
|
||||
for selection_dict in self.current_selection:
|
||||
if selection_dict["full_element_name"] == full_element_name:
|
||||
selection_dict_to_remove = selection_dict
|
||||
break
|
||||
|
||||
if selection_dict_to_remove is not None:
|
||||
self.current_selection.remove(selection_dict_to_remove)
|
||||
|
||||
self.updateJoint()
|
||||
|
||||
def setPreselection(self, doc_name, obj_name, sub_name):
|
||||
if not sub_name:
|
||||
self.preselection_dict = None
|
||||
return
|
||||
|
||||
full_obj_name = UtilsAssembly.getFullObjName(obj_name, sub_name)
|
||||
full_element_name = UtilsAssembly.getFullElementName(obj_name, sub_name)
|
||||
selected_object = UtilsAssembly.getObject(full_element_name)
|
||||
element_name = UtilsAssembly.getElementName(full_element_name)
|
||||
|
||||
self.preselection_dict = {
|
||||
"object": selected_object,
|
||||
"sub_name": sub_name,
|
||||
"element_name": element_name,
|
||||
"full_element_name": full_element_name,
|
||||
"full_obj_name": full_obj_name,
|
||||
}
|
||||
|
||||
def clearSelection(self, doc_name):
|
||||
self.current_selection.clear()
|
||||
self.updateJoint()
|
||||
|
||||
|
||||
if App.GuiUp:
|
||||
Gui.addCommand("Assembly_ToggleGrounded", CommandToggleGrounded())
|
||||
Gui.addCommand("Assembly_CreateJointFixed", CommandCreateJointFixed())
|
||||
Gui.addCommand("Assembly_CreateJointRevolute", CommandCreateJointRevolute())
|
||||
Gui.addCommand("Assembly_CreateJointCylindrical", CommandCreateJointCylindrical())
|
||||
Gui.addCommand("Assembly_CreateJointSlider", CommandCreateJointSlider())
|
||||
Gui.addCommand("Assembly_CreateJointBall", CommandCreateJointBall())
|
||||
Gui.addCommand("Assembly_CreateJointPlanar", CommandCreateJointPlanar())
|
||||
Gui.addCommand("Assembly_CreateJointParallel", CommandCreateJointParallel())
|
||||
Gui.addCommand("Assembly_CreateJointTangent", CommandCreateJointTangent())
|
||||
Gui.addCommand("Assembly_CreateJointDistance", CommandCreateJointDistance())
|
||||
|
||||
82
src/Mod/Assembly/CommandExportASMT.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
# 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 *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# **************************************************************************/
|
||||
|
||||
import FreeCAD as App
|
||||
import UtilsAssembly
|
||||
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
from PySide.QtWidgets import QFileDialog
|
||||
|
||||
if App.GuiUp:
|
||||
import FreeCADGui as Gui
|
||||
|
||||
# translate = App.Qt.translate
|
||||
|
||||
__title__ = "Assembly Command Create Assembly"
|
||||
__author__ = "Ondsel"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
|
||||
class CommandExportASMT:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def GetResources(self):
|
||||
return {
|
||||
"Pixmap": "Assembly_ExportASMT",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Assembly_ExportASMT", "Export ASMT File"),
|
||||
"Accel": "E",
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"Assembly_ExportASMT",
|
||||
"Export currently active assembly as a ASMT file.",
|
||||
),
|
||||
"CmdType": "ForEdit",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
return UtilsAssembly.isAssemblyCommandActive() and UtilsAssembly.isAssemblyGrounded()
|
||||
|
||||
def Activated(self):
|
||||
document = App.ActiveDocument
|
||||
if not document:
|
||||
return
|
||||
|
||||
assembly = UtilsAssembly.activeAssembly()
|
||||
if not assembly:
|
||||
return
|
||||
|
||||
# Prompt the user for a file location and name
|
||||
defaultFileName = document.Name + ".asmt"
|
||||
filePath, _ = QFileDialog.getSaveFileName(
|
||||
None,
|
||||
"Save ASMT File",
|
||||
defaultFileName,
|
||||
"ASMT Files (*.asmt);;All Files (*)",
|
||||
)
|
||||
|
||||
if filePath:
|
||||
assembly.exportAsASMT(filePath)
|
||||
|
||||
|
||||
if App.GuiUp:
|
||||
Gui.addCommand("Assembly_ExportASMT", CommandExportASMT())
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,8 +19,9 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
import re
|
||||
import os
|
||||
import FreeCAD as App
|
||||
|
||||
@@ -31,6 +32,8 @@ if App.GuiUp:
|
||||
from PySide import QtCore, QtGui, QtWidgets
|
||||
|
||||
import UtilsAssembly
|
||||
import Preferences
|
||||
import CommandCreateJoint
|
||||
|
||||
# translate = App.Qt.translate
|
||||
|
||||
@@ -44,22 +47,32 @@ class CommandInsertLink:
|
||||
pass
|
||||
|
||||
def GetResources(self):
|
||||
tooltip = "<p>Insert a Link into the assembly. "
|
||||
tooltip += "This will create dynamic links to parts/bodies/primitives/assemblies."
|
||||
tooltip += "To insert external objects, make sure that the file "
|
||||
tooltip += "is <b>open in the current session</b></p>"
|
||||
tooltip += "<p>Press shift to add several links while clicking on the view."
|
||||
|
||||
return {
|
||||
"Pixmap": "Assembly_InsertLink",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Assembly_InsertLink", "Insert Link"),
|
||||
"Accel": "I",
|
||||
"ToolTip": QT_TRANSLATE_NOOP("Assembly_InsertLink", tooltip),
|
||||
"ToolTip": "<p>"
|
||||
+ QT_TRANSLATE_NOOP(
|
||||
"Assembly_InsertLink",
|
||||
"Insert a Link into the currently active assembly. This will create dynamic links to parts/bodies/primitives/assemblies. To insert external objects, make sure that the file is <b>open in the current session</b>",
|
||||
)
|
||||
+ "</p><p><ul><li>"
|
||||
+ QT_TRANSLATE_NOOP("Assembly_InsertLink", "Insert by left clicking items in the list.")
|
||||
+ "</li><li>"
|
||||
+ QT_TRANSLATE_NOOP(
|
||||
"Assembly_InsertLink", "Remove by right clicking items in the list."
|
||||
)
|
||||
+ "</li><li>"
|
||||
+ QT_TRANSLATE_NOOP(
|
||||
"Assembly_InsertLink",
|
||||
"Press shift to add several links while clicking on the view.",
|
||||
)
|
||||
+ "</li></ul></p>",
|
||||
"CmdType": "ForEdit",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
return UtilsAssembly.activeAssembly() is not None
|
||||
return UtilsAssembly.isAssemblyCommandActive()
|
||||
|
||||
def Activated(self):
|
||||
assembly = UtilsAssembly.activeAssembly()
|
||||
@@ -81,6 +94,10 @@ class TaskAssemblyInsertLink(QtCore.QObject):
|
||||
|
||||
self.form = Gui.PySideUic.loadUi(":/panels/TaskAssemblyInsertLink.ui")
|
||||
self.form.installEventFilter(self)
|
||||
self.form.partList.installEventFilter(self)
|
||||
|
||||
pref = Preferences.preferences()
|
||||
self.form.CheckBox_InsertInParts.setChecked(pref.GetBool("InsertInParts", True))
|
||||
|
||||
# Actions
|
||||
self.form.openFileButton.clicked.connect(self.openFiles)
|
||||
@@ -89,27 +106,38 @@ class TaskAssemblyInsertLink(QtCore.QObject):
|
||||
|
||||
self.allParts = []
|
||||
self.partsDoc = []
|
||||
self.numberOfAddedParts = 0
|
||||
self.translation = 0
|
||||
self.partMoving = False
|
||||
self.totalTranslation = App.Vector()
|
||||
self.groundedObj = None
|
||||
|
||||
self.insertionStack = [] # used to handle cancellation of insertions.
|
||||
|
||||
self.buildPartList()
|
||||
|
||||
App.setActiveTransaction("Insert Link")
|
||||
|
||||
def accept(self):
|
||||
App.closeActiveTransaction()
|
||||
self.deactivated()
|
||||
|
||||
if self.partMoving:
|
||||
self.endMove()
|
||||
|
||||
App.closeActiveTransaction()
|
||||
return True
|
||||
|
||||
def reject(self):
|
||||
App.closeActiveTransaction(True)
|
||||
self.deactivated()
|
||||
|
||||
if self.partMoving:
|
||||
self.dismissPart()
|
||||
|
||||
App.closeActiveTransaction(True)
|
||||
return True
|
||||
|
||||
def deactivated(self):
|
||||
if self.partMoving:
|
||||
self.endMove()
|
||||
pref = Preferences.preferences()
|
||||
pref.SetBool("InsertInParts", self.form.CheckBox_InsertInParts.isChecked())
|
||||
|
||||
def buildPartList(self):
|
||||
self.allParts.clear()
|
||||
@@ -135,7 +163,7 @@ class TaskAssemblyInsertLink(QtCore.QObject):
|
||||
self.allParts.append(obj)
|
||||
self.partsDoc.append(doc)
|
||||
|
||||
for obj in doc.findObjects("PartDesign::Body"):
|
||||
for obj in doc.findObjects("Part::Feature"):
|
||||
# but only those at top level (not nested inside other containers)
|
||||
if obj.getParentGeoFeatureGroup() is None:
|
||||
self.allParts.append(obj)
|
||||
@@ -144,7 +172,7 @@ class TaskAssemblyInsertLink(QtCore.QObject):
|
||||
self.form.partList.clear()
|
||||
for part in self.allParts:
|
||||
newItem = QtGui.QListWidgetItem()
|
||||
newItem.setText(part.Document.Name + " - " + part.Name)
|
||||
newItem.setText(part.Label + " (" + part.Document.Name + ".FCStd)")
|
||||
newItem.setIcon(part.ViewObject.Icon)
|
||||
self.form.partList.addItem(newItem)
|
||||
|
||||
@@ -192,23 +220,125 @@ class TaskAssemblyInsertLink(QtCore.QObject):
|
||||
|
||||
# check that the current document had been saved or that it's the same document as that of the selected part
|
||||
if not self.doc.FileName != "" and not self.doc == selectedPart.Document:
|
||||
print("The current document must be saved before inserting an external part")
|
||||
return
|
||||
msgBox = QtWidgets.QMessageBox()
|
||||
msgBox.setIcon(QtWidgets.QMessageBox.Warning)
|
||||
msgBox.setText("The current document must be saved before inserting external parts.")
|
||||
msgBox.setWindowTitle("Save Document")
|
||||
saveButton = msgBox.addButton("Save", QtWidgets.QMessageBox.AcceptRole)
|
||||
cancelButton = msgBox.addButton("Cancel", QtWidgets.QMessageBox.RejectRole)
|
||||
|
||||
self.createdLink = self.assembly.newObject("App::Link", selectedPart.Name)
|
||||
self.createdLink.LinkedObject = selectedPart
|
||||
self.createdLink.Placement.Base = self.getTranslationVec(selectedPart)
|
||||
self.createdLink.recompute()
|
||||
msgBox.exec_()
|
||||
|
||||
self.numberOfAddedParts += 1
|
||||
if not (msgBox.clickedButton() == saveButton and Gui.ActiveDocument.saveAs()):
|
||||
return
|
||||
|
||||
objectWhereToInsert = self.assembly
|
||||
|
||||
if self.form.CheckBox_InsertInParts.isChecked() and selectedPart.TypeId != "App::Part":
|
||||
objectWhereToInsert = self.assembly.newObject("App::Part", "Part_" + selectedPart.Label)
|
||||
|
||||
createdLink = objectWhereToInsert.newObject("App::Link", selectedPart.Label)
|
||||
createdLink.LinkedObject = selectedPart
|
||||
createdLink.recompute()
|
||||
|
||||
addedObject = createdLink
|
||||
if self.form.CheckBox_InsertInParts.isChecked() and selectedPart.TypeId != "App::Part":
|
||||
addedObject = objectWhereToInsert
|
||||
|
||||
insertionDict = {}
|
||||
insertionDict["item"] = item
|
||||
insertionDict["addedObject"] = addedObject
|
||||
self.insertionStack.append(insertionDict)
|
||||
self.increment_counter(item)
|
||||
|
||||
translation = self.getTranslationVec(addedObject)
|
||||
insertionDict["translation"] = translation
|
||||
self.totalTranslation += translation
|
||||
addedObject.Placement.Base = self.totalTranslation
|
||||
|
||||
# highlight the link
|
||||
Gui.Selection.clearSelection()
|
||||
Gui.Selection.addSelection(self.doc.Name, self.assembly.Name, self.createdLink.Name + ".")
|
||||
Gui.Selection.addSelection(self.doc.Name, addedObject.Name, "")
|
||||
|
||||
# Start moving the part if user brings mouse on view
|
||||
self.initMove()
|
||||
|
||||
self.form.partList.setItemSelected(item, False)
|
||||
|
||||
if len(self.insertionStack) == 1 and not UtilsAssembly.isAssemblyGrounded():
|
||||
self.handleFirstInsertion()
|
||||
|
||||
def handleFirstInsertion(self):
|
||||
pref = Preferences.preferences()
|
||||
fixPart = False
|
||||
fixPartPref = pref.GetInt("GroundFirstPart", 0)
|
||||
if fixPartPref == 0: # unset
|
||||
msgBox = QtWidgets.QMessageBox()
|
||||
msgBox.setWindowTitle("Ground Part?")
|
||||
msgBox.setText(
|
||||
"Do you want to ground the first inserted part automatically?\nYou need at least one grounded part in your assembly."
|
||||
)
|
||||
msgBox.setIcon(QtWidgets.QMessageBox.Question)
|
||||
|
||||
yesButton = msgBox.addButton("Yes", QtWidgets.QMessageBox.YesRole)
|
||||
noButton = msgBox.addButton("No", QtWidgets.QMessageBox.NoRole)
|
||||
yesAlwaysButton = msgBox.addButton("Always", QtWidgets.QMessageBox.YesRole)
|
||||
noAlwaysButton = msgBox.addButton("Never", QtWidgets.QMessageBox.NoRole)
|
||||
|
||||
msgBox.exec_()
|
||||
|
||||
clickedButton = msgBox.clickedButton()
|
||||
if clickedButton == yesButton:
|
||||
fixPart = True
|
||||
elif clickedButton == yesAlwaysButton:
|
||||
fixPart = True
|
||||
pref.SetInt("GroundFirstPart", 1)
|
||||
elif clickedButton == noAlwaysButton:
|
||||
pref.SetInt("GroundFirstPart", 2)
|
||||
|
||||
elif fixPartPref == 1: # Yes always
|
||||
fixPart = True
|
||||
|
||||
if fixPart:
|
||||
# Create groundedJoint.
|
||||
if len(self.insertionStack) != 1:
|
||||
return
|
||||
|
||||
self.groundedObj = self.insertionStack[0]["addedObject"]
|
||||
self.groundedJoint = CommandCreateJoint.createGroundedJoint(self.groundedObj)
|
||||
self.endMove()
|
||||
|
||||
def increment_counter(self, item):
|
||||
text = item.text()
|
||||
match = re.search(r"(\d+) inserted$", text)
|
||||
|
||||
if match:
|
||||
# Counter exists, increment it
|
||||
counter = int(match.group(1)) + 1
|
||||
new_text = re.sub(r"\d+ inserted$", f"{counter} inserted", text)
|
||||
else:
|
||||
# Counter does not exist, add it
|
||||
new_text = f"{text} : 1 inserted"
|
||||
|
||||
item.setText(new_text)
|
||||
|
||||
def decrement_counter(self, item):
|
||||
text = item.text()
|
||||
match = re.search(r"(\d+) inserted$", text)
|
||||
|
||||
if match:
|
||||
counter = int(match.group(1)) - 1
|
||||
if counter > 0:
|
||||
# Update the counter
|
||||
new_text = re.sub(r"\d+ inserted$", f"{counter} inserted", text)
|
||||
elif counter == 0:
|
||||
# Remove the counter part from the text
|
||||
new_text = re.sub(r" : \d+ inserted$", "", text)
|
||||
else:
|
||||
return
|
||||
|
||||
item.setText(new_text)
|
||||
|
||||
def initMove(self):
|
||||
self.callbackMove = self.view.addEventCallback("SoLocation2Event", self.moveMouse)
|
||||
self.callbackClick = self.view.addEventCallback("SoMouseButtonEvent", self.clickMouse)
|
||||
@@ -229,42 +359,87 @@ class TaskAssemblyInsertLink(QtCore.QObject):
|
||||
|
||||
def moveMouse(self, info):
|
||||
newPos = self.view.getPoint(*info["Position"])
|
||||
self.createdLink.Placement.Base = newPos
|
||||
self.insertionStack[-1]["addedObject"].Placement.Base = newPos
|
||||
|
||||
def clickMouse(self, info):
|
||||
if info["Button"] == "BUTTON1" and info["State"] == "DOWN":
|
||||
Gui.Selection.clearSelection()
|
||||
if info["ShiftDown"]:
|
||||
# Create a new link and moves this one now
|
||||
currentPos = self.createdLink.Placement.Base
|
||||
selectedPart = self.createdLink.LinkedObject
|
||||
self.createdLink = self.assembly.newObject("App::Link", selectedPart.Name)
|
||||
self.createdLink.LinkedObject = selectedPart
|
||||
self.createdLink.Placement.Base = currentPos
|
||||
addedObject = self.insertionStack[-1]["addedObject"]
|
||||
currentPos = addedObject.Placement.Base
|
||||
selectedPart = addedObject
|
||||
if addedObject.TypeId == "App::Link":
|
||||
selectedPart = addedObject.LinkedObject
|
||||
|
||||
addedObject = self.assembly.newObject("App::Link", selectedPart.Label)
|
||||
addedObject.LinkedObject = selectedPart
|
||||
addedObject.Placement.Base = currentPos
|
||||
|
||||
insertionDict = {}
|
||||
insertionDict["translation"] = App.Vector()
|
||||
insertionDict["item"] = self.insertionStack[-1]["item"]
|
||||
insertionDict["addedObject"] = addedObject
|
||||
self.insertionStack.append(insertionDict)
|
||||
|
||||
else:
|
||||
self.endMove()
|
||||
|
||||
elif info["Button"] == "BUTTON2" and info["State"] == "DOWN":
|
||||
self.dismissPart()
|
||||
|
||||
# 3D view keyboard handler
|
||||
def KeyboardEvent(self, info):
|
||||
if info["State"] == "UP" and info["Key"] == "ESCAPE":
|
||||
self.endMove()
|
||||
self.doc.removeObject(self.createdLink.Name)
|
||||
self.dismissPart()
|
||||
|
||||
def dismissPart(self):
|
||||
self.endMove()
|
||||
stack_item = self.insertionStack.pop()
|
||||
self.totalTranslation -= stack_item["translation"]
|
||||
UtilsAssembly.removeObjAndChilds(stack_item["addedObject"])
|
||||
self.decrement_counter(stack_item["item"])
|
||||
|
||||
# Taskbox keyboard event handler
|
||||
def eventFilter(self, watched, event):
|
||||
if watched == self.form and event.type() == QtCore.QEvent.KeyPress:
|
||||
if event.key() == QtCore.Qt.Key_Escape and self.partMoving:
|
||||
self.endMove()
|
||||
self.doc.removeObject(self.createdLink.Name)
|
||||
self.dismissPart()
|
||||
return True # Consume the event
|
||||
|
||||
if event.type() == QtCore.QEvent.ContextMenu and watched is self.form.partList:
|
||||
item = watched.itemAt(event.pos())
|
||||
|
||||
if item:
|
||||
# Iterate through the insertionStack in reverse
|
||||
for i in reversed(range(len(self.insertionStack))):
|
||||
stack_item = self.insertionStack[i]
|
||||
|
||||
if stack_item["item"] == item:
|
||||
if self.partMoving:
|
||||
self.endMove()
|
||||
|
||||
self.totalTranslation -= stack_item["translation"]
|
||||
obj = stack_item["addedObject"]
|
||||
if self.groundedObj == obj:
|
||||
self.groundedJoint.Document.removeObject(self.groundedJoint.Name)
|
||||
UtilsAssembly.removeObjAndChilds(obj)
|
||||
|
||||
self.decrement_counter(item)
|
||||
del self.insertionStack[i]
|
||||
self.form.partList.setItemSelected(item, False)
|
||||
|
||||
return True
|
||||
|
||||
return super().eventFilter(watched, event)
|
||||
|
||||
def getTranslationVec(self, part):
|
||||
bb = part.Shape.BoundBox
|
||||
if bb:
|
||||
self.translation += (bb.XMax + bb.YMax + bb.ZMax) * 0.15
|
||||
translation = (bb.XMax + bb.YMax + bb.ZMax) * 0.15
|
||||
else:
|
||||
self.translation += 10
|
||||
return App.Vector(self.translation, self.translation, self.translation)
|
||||
translation = 10
|
||||
return App.Vector(translation, translation, translation)
|
||||
|
||||
|
||||
if App.GuiUp:
|
||||
|
||||
76
src/Mod/Assembly/CommandSolveAssembly.py
Normal file
@@ -0,0 +1,76 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
# 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 *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# **************************************************************************/
|
||||
|
||||
import os
|
||||
import FreeCAD as App
|
||||
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
if App.GuiUp:
|
||||
import FreeCADGui as Gui
|
||||
from PySide import QtCore, QtGui, QtWidgets
|
||||
|
||||
import UtilsAssembly
|
||||
import Assembly_rc
|
||||
|
||||
# translate = App.Qt.translate
|
||||
|
||||
__title__ = "Assembly Command to Solve Assembly"
|
||||
__author__ = "Ondsel"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
|
||||
class CommandSolveAssembly:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def GetResources(self):
|
||||
|
||||
return {
|
||||
"Pixmap": "Assembly_SolveAssembly",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Assembly_SolveAssembly", "Solve Assembly"),
|
||||
"Accel": "Z",
|
||||
"ToolTip": "<p>"
|
||||
+ QT_TRANSLATE_NOOP(
|
||||
"Assembly_SolveAssembly",
|
||||
"Solve the currently active assembly.",
|
||||
)
|
||||
+ "</p>",
|
||||
"CmdType": "ForEdit",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
return UtilsAssembly.isAssemblyCommandActive() and UtilsAssembly.isAssemblyGrounded()
|
||||
|
||||
def Activated(self):
|
||||
assembly = UtilsAssembly.activeAssembly()
|
||||
if not assembly:
|
||||
return
|
||||
|
||||
App.setActiveTransaction("Solve assembly")
|
||||
assembly.solve()
|
||||
App.closeActiveTransaction()
|
||||
|
||||
|
||||
if App.GuiUp:
|
||||
Gui.addCommand("Assembly_SolveAssembly", CommandSolveAssembly())
|
||||
53
src/Mod/Assembly/Gui/AppAssemblyGui.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#include <Base/Console.h>
|
||||
#include <Base/PyObjectBase.h>
|
||||
|
||||
#include "ViewProviderAssembly.h"
|
||||
#include "ViewProviderJointGroup.h"
|
||||
|
||||
|
||||
namespace AssemblyGui
|
||||
{
|
||||
extern PyObject* initModule();
|
||||
}
|
||||
|
||||
/* Python entry */
|
||||
PyMOD_INIT_FUNC(AssemblyGui)
|
||||
{
|
||||
PyObject* mod = AssemblyGui::initModule();
|
||||
Base::Console().Log("Loading AssemblyGui module... done\n");
|
||||
|
||||
|
||||
// NOTE: To finish the initialization of our own type objects we must
|
||||
// call PyType_Ready, otherwise we run into a segmentation fault, later on.
|
||||
// This function is responsible for adding inherited slots from a type's base class.
|
||||
|
||||
AssemblyGui::ViewProviderAssembly ::init();
|
||||
AssemblyGui::ViewProviderJointGroup::init();
|
||||
|
||||
PyMOD_Return(mod);
|
||||
}
|
||||
46
src/Mod/Assembly/Gui/AppAssemblyGuiPy.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#include <Base/Interpreter.h>
|
||||
|
||||
|
||||
namespace AssemblyGui
|
||||
{
|
||||
class Module: public Py::ExtensionModule<Module>
|
||||
{
|
||||
public:
|
||||
Module()
|
||||
: Py::ExtensionModule<Module>("AssemblyGui")
|
||||
{
|
||||
initialize("This module is the Assembly module."); // register with Python
|
||||
}
|
||||
};
|
||||
|
||||
PyObject* initModule()
|
||||
{
|
||||
return Base::Interpreter().addModule(new Module);
|
||||
}
|
||||
|
||||
} // namespace AssemblyGui
|
||||
@@ -1,9 +1,13 @@
|
||||
include_directories(
|
||||
${CMAKE_BINARY_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${OCC_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
set(AssemblyGui_LIBS
|
||||
Assembly
|
||||
PartDesign
|
||||
PartGui
|
||||
FreeCADGui
|
||||
)
|
||||
|
||||
@@ -17,8 +21,23 @@ qt_add_resources(AssemblyResource_SRCS Resources/Assembly.qrc ${Assembly_TR_QRC}
|
||||
|
||||
SOURCE_GROUP("Resources" FILES ${AssemblyResource_SRCS})
|
||||
|
||||
generate_from_xml(ViewProviderAssemblyPy)
|
||||
|
||||
SET(Python_SRCS
|
||||
ViewProviderAssemblyPy.xml
|
||||
ViewProviderAssemblyPyImp.cpp
|
||||
)
|
||||
SOURCE_GROUP("Python" FILES ${Python_SRCS})
|
||||
|
||||
SET(AssemblyGui_SRCS_Module
|
||||
AppAssemblyGui.cpp
|
||||
AppAssemblyGuiPy.cpp
|
||||
PreCompiled.cpp
|
||||
PreCompiled.h
|
||||
ViewProviderAssembly.cpp
|
||||
ViewProviderAssembly.h
|
||||
ViewProviderJointGroup.cpp
|
||||
ViewProviderJointGroup.h
|
||||
${Assembly_QRC_SRCS}
|
||||
)
|
||||
|
||||
@@ -29,8 +48,14 @@ SET(AssemblyGui_SRCS
|
||||
${AssemblyResource_SRCS}
|
||||
${AssemblyGui_UIC_HDRS}
|
||||
${AssemblyGui_SRCS_Module}
|
||||
${Python_SRCS}
|
||||
)
|
||||
|
||||
if(FREECAD_USE_PCH)
|
||||
add_definitions(-D_PreComp_)
|
||||
GET_MSVC_PRECOMPILED_SOURCE("PreCompiled.cpp" PCH_SRCS ${AssemblyGui_SRCS})
|
||||
ADD_MSVC_PRECOMPILED_HEADER(PathGui PreCompiled.h PreCompiled.cpp PCH_SRCS)
|
||||
endif(FREECAD_USE_PCH)
|
||||
|
||||
SET(AssemblyGuiIcon_SVG
|
||||
Resources/icons/AssemblyWorkbench.svg
|
||||
|
||||
25
src/Mod/Assembly/Gui/PreCompiled.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#include "PreCompiled.h"
|
||||
54
src/Mod/Assembly/Gui/PreCompiled.h
Normal file
@@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef POINTSGUI_PRECOMPILED_H
|
||||
#define POINTSGUI_PRECOMPILED_H
|
||||
|
||||
#include <FCConfig.h>
|
||||
|
||||
#ifdef _PreComp_
|
||||
|
||||
// STL
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
// Qt
|
||||
#ifndef __QtAll__
|
||||
#include <Gui/QtAll.h>
|
||||
#endif
|
||||
|
||||
#include <QWidgetAction>
|
||||
|
||||
// all of Inventor
|
||||
#ifndef __InventorAll__
|
||||
#include <Gui/InventorAll.h>
|
||||
#endif
|
||||
|
||||
#endif //_PreComp_
|
||||
|
||||
#endif // POINTSGUI_PRECOMPILED_H
|
||||
@@ -1,17 +1,20 @@
|
||||
<RCC>
|
||||
<qresource>
|
||||
<qresource prefix="/">
|
||||
<file>icons/Assembly_InsertLink.svg</file>
|
||||
<file>icons/preferences-assembly.svg</file>
|
||||
<file>icons/Assembly_ToggleGrounded.svg</file>
|
||||
<file>icons/Assembly_CreateJointBall.svg</file>
|
||||
<file>icons/Assembly_CreateJointCylindrical.svg</file>
|
||||
<file>icons/Assembly_CreateJointFixed.svg</file>
|
||||
<file>icons/Assembly_CreateJointParallel.svg</file>
|
||||
<file>icons/Assembly_CreateJointPlanar.svg</file>
|
||||
<file>icons/Assembly_CreateJointRevolute.svg</file>
|
||||
<file>icons/Assembly_CreateJointSlider.svg</file>
|
||||
<file>icons/Assembly_CreateJointTangent.svg</file>
|
||||
<file>icons/Assembly_ExportASMT.svg</file>
|
||||
<file>icons/Assembly_SolveAssembly.svg</file>
|
||||
<file>panels/TaskAssemblyCreateJoint.ui</file>
|
||||
<file>panels/TaskAssemblyInsertLink.ui</file>
|
||||
<file>preferences/Assembly.ui</file>
|
||||
<file>icons/Assembly_CreateJointDistance.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
@@ -7,7 +7,7 @@
|
||||
id="svg2821"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
sodipodi:docname="Assembly_CreateJointFixed.svg"
|
||||
sodipodi:docname="Assembly_CreateJointFixedNew.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
||||
version="1.1"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
@@ -175,6 +175,17 @@
|
||||
x2="85"
|
||||
y2="35"
|
||||
spreadMethod="reflect" />
|
||||
<linearGradient
|
||||
id="linearGradient4067-6">
|
||||
<stop
|
||||
style="stop-color:#888a85;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4069-7" />
|
||||
<stop
|
||||
style="stop-color:#2e3436;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop4071-5" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
@@ -183,21 +194,22 @@
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="10.193662"
|
||||
inkscape:cx="30.950604"
|
||||
inkscape:cy="29.184801"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:zoom="7.2080076"
|
||||
inkscape:cx="44.186968"
|
||||
inkscape:cy="8.4628102"
|
||||
inkscape:current-layer="g2"
|
||||
showgrid="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1355"
|
||||
inkscape:window-height="1356"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1">
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showguides="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2992"
|
||||
@@ -210,6 +222,27 @@
|
||||
spacingy="1"
|
||||
spacingx="1"
|
||||
units="px" />
|
||||
<sodipodi:guide
|
||||
position="32,65.942284"
|
||||
orientation="-1,0"
|
||||
id="guide1"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,134,229)" />
|
||||
<sodipodi:guide
|
||||
position="22,67.121528"
|
||||
orientation="-1,0"
|
||||
id="guide2"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,134,229)" />
|
||||
<sodipodi:guide
|
||||
position="42,66.289121"
|
||||
orientation="-1,0"
|
||||
id="guide3"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,134,229)" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata2826">
|
||||
@@ -261,46 +294,51 @@
|
||||
transform="translate(3.6192085e-6,-0.89630564)">
|
||||
<path
|
||||
style="fill:#fce94f;stroke:#302b00;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.5;stroke-dashoffset:20.4;stroke-opacity:1"
|
||||
d="m 56.000001,52.8038 c 0,4.418278 -10.745166,8 -24,8 -13.254833,0 -23.9999985,-3.581722 -23.9999985,-8 l -2e-6,-18.217405 H 56 Z"
|
||||
d="m 56.000001,52.8038 c 0,4.418278 -10.745166,8 -24,8 -13.254833,0 -23.9999985,-3.581722 -23.9999985,-8 l -2e-6,-22.788679 L 56,30.015122 Z"
|
||||
id="path2994-3"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sscccs" />
|
||||
<path
|
||||
style="fill:url(#linearGradient3807);fill-opacity:1;stroke:#fce94f;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.5;stroke-dashoffset:20.4;stroke-opacity:1"
|
||||
d="m 54,51.531073 c 0,4.016616 -9.84973,7.272727 -22,7.272727 -12.150264,0 -21.999999,-3.256111 -21.999999,-7.272727 l -1.5e-6,-15.950307 H 54 Z"
|
||||
d="m 54,51.531073 c 0,4.016616 -9.84973,7.272727 -22,7.272727 -12.150264,0 -21.999999,-3.256111 -21.999999,-7.272727 l -1.5e-6,-17.675951 H 54 Z"
|
||||
id="path2994-3-6"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sscccs" />
|
||||
<path
|
||||
style="fill:none;stroke:#fce94f;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.5;stroke-dashoffset:20.4;stroke-opacity:1"
|
||||
d="m 54,38.835626 c -2,3.272727 -9.84973,5.272727 -22,5.272727 -12.150264,0 -19,-2 -21.999999,-5.272727"
|
||||
d="m 53.883463,36.735122 c -2.106548,1.373238 -5.918402,2.880001 -9.403465,3.127242 l -0.005,9.10552 c 0,0 -4.321609,1.454482 -12.474967,1.454482 -8.153358,0 -12.5599,-1.348891 -12.5599,-1.348891 l 0.07989,-9.211111 c -2.251676,-0.247241 -6.445502,-1.207241 -9.370239,-3.127242"
|
||||
id="path2994-3-6-9"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csc" />
|
||||
sodipodi:nodetypes="ccczccc" />
|
||||
<ellipse
|
||||
style="fill:#fce94f;stroke:#302b00;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.5;stroke-dasharray:none;stroke-dashoffset:20.4;stroke-opacity:1"
|
||||
id="path2994"
|
||||
cx="32"
|
||||
cy="34.381172"
|
||||
rx="24"
|
||||
ry="7.9999995" />
|
||||
cy="31.700123"
|
||||
ry="7.9999995"
|
||||
rx="24" />
|
||||
</g>
|
||||
<path
|
||||
d="m 42.539277,35.800977 0.02691,10.547767 c 0,0 -3.169859,1.167493 -10.566183,1.116355 -7.396326,-0.05114 -10.566196,-1.116355 -10.566196,-1.116355 l -0.02323,-10.555835"
|
||||
style="fill:none;fill-opacity:1;stroke:#302b00;stroke-width:2.22135;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.5;stroke-dashoffset:20.4;stroke-opacity:1"
|
||||
id="path1-6"
|
||||
sodipodi:nodetypes="ccscc" />
|
||||
<g
|
||||
id="g3"
|
||||
inkscape:label="Part 2"
|
||||
transform="translate(3.6192085e-6,-20.496033)">
|
||||
<path
|
||||
style="fill:#729fcf;fill-opacity:1;stroke:#0b1521;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.5;stroke-dashoffset:20.4;stroke-opacity:1"
|
||||
d="m 56.000001,52.8038 c 0,4.418278 -10.745166,8 -24,8 -13.254833,0 -23.9999985,-3.581722 -23.9999985,-8 l -2e-6,-18.217405 H 56 Z"
|
||||
d="m 56,49.614849 c -10e-7,3.283664 -5.935052,6.10526 -14.424452,7.337886 l 0.02445,8.673166 c 0,0 -2.880002,0.96 -9.599996,0.917951 -6.719994,-0.04205 -9.600007,-0.917951 -9.600007,-0.917951 l -0.0211,-8.6798 C 13.913825,55.710006 8.000001,52.892635 8.000001,49.614849 l -5e-7,-15.028454 H 56 Z"
|
||||
id="path1"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sscccs" />
|
||||
sodipodi:nodetypes="scczcccccs" />
|
||||
<path
|
||||
style="fill:url(#linearGradient3);fill-opacity:1;stroke:#729fcf;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.5;stroke-dashoffset:20.4;stroke-opacity:1"
|
||||
d="m 54,51.531073 c 0,4.016616 -9.84973,7.272727 -22,7.272727 -12.150264,0 -21.999999,-3.256111 -21.999999,-7.272727 l -1.5e-6,-15.950307 H 54 Z"
|
||||
style="fill:url(#linearGradient3);fill-opacity:1;stroke:#729fcf;stroke-width:1.68;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.5;stroke-dasharray:none;stroke-dashoffset:20.4;stroke-opacity:1"
|
||||
d="m 54.079999,48.654848 c 0.01917,3.133054 -5.916694,5.700603 -14.320003,6.723777 V 63.8129 c 0,0 -2.247749,0.778588 -7.76,0.778588 -5.512251,0 -7.600003,-0.572862 -7.600003,-0.572862 V 55.542947 C 16.171126,54.490553 10.08,51.746576 10.08,48.654848 L 9.9999995,35.580766 H 54 Z"
|
||||
id="path2"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sscccs" />
|
||||
sodipodi:nodetypes="scczcccccs" />
|
||||
<path
|
||||
style="fill:none;stroke:#729fcf;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.5;stroke-dashoffset:20.4;stroke-opacity:1"
|
||||
d="m 54,38.835626 c -2,3.272727 -9.84973,5.272727 -22,5.272727 -12.150264,0 -19,-2 -21.999999,-5.272727"
|
||||
@@ -315,51 +353,9 @@
|
||||
rx="24"
|
||||
ry="7.9999995" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.55958744,0,0,1.0254139,7.7599462,8.7187646)"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:54.2152px;font-family:Arial;-inkscape-font-specification:Arial;display:inline;overflow:visible;visibility:visible;fill:#ff2600;fill-opacity:1;fill-rule:nonzero;stroke:#731200;stroke-width:2.19132;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
id="text3796"
|
||||
inkscape:label="Lock">
|
||||
<g
|
||||
transform="matrix(0.26232603,0,0,0.14315619,-698.74089,-70.421371)"
|
||||
id="g2385"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:54.2152px;font-family:Arial;-inkscape-font-specification:Arial;display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient3844);fill-opacity:1;fill-rule:nonzero;stroke:#042a2a;stroke-width:10.3206;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate">
|
||||
<path
|
||||
style="display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient3045);fill-opacity:1;fill-rule:evenodd;stroke:#280000;stroke-width:10.3206;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m 2751.3603,595.12568 v 0 0 l -2e-4,46.44262 h 30.9618 l -0.2475,-47.17448 h -0.05 c 0.2977,-25.0696 20.9388,-45.71077 46.7403,-45.71077 25.8014,0 46.4426,20.64117 46.4421,46.44263 v 0 46.44262 h 30.9618 v -46.44262 0 c 5e-4,-41.28234 -25.801,-77.40438 -77.4039,-77.40438 -51.6029,0 -77.4044,36.12204 -77.4044,77.40438 z"
|
||||
id="path2387" />
|
||||
<rect
|
||||
style="display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient3880);fill-opacity:1;fill-rule:evenodd;stroke:#280000;stroke-width:10.3206;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
id="rect2389"
|
||||
width="196.09097"
|
||||
height="154.80875"
|
||||
x="2730.7192"
|
||||
y="641.5683" />
|
||||
<rect
|
||||
style="display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ef2929;stroke-width:10.3206;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
id="rect2389-0"
|
||||
width="175.44977"
|
||||
height="134.16759"
|
||||
x="2741.0398"
|
||||
y="651.88885" />
|
||||
</g>
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:54.2152px;font-family:Arial;-inkscape-font-specification:Arial;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#a40000;stroke-width:4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="M 25.719895,26.594196 H 60.915549"
|
||||
id="path3777-7" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:54.2152px;font-family:Arial;-inkscape-font-specification:Arial;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#a40000;stroke-width:4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="M 25.719921,32.504016 H 60.915574"
|
||||
id="path3777-3-5" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:54.2152px;font-family:Arial;-inkscape-font-specification:Arial;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#a40000;stroke-width:4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="M 25.719921,38.413838 H 60.915574"
|
||||
id="path3777-6-3" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:54.2152px;font-family:Arial;-inkscape-font-specification:Arial;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ef2929;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m 25.719879,20.684376 v -6.648549 c 1.2e-5,-3.69364 5.414701,-8.8647326 17.597824,-8.8647334 12.183122,-7e-7 17.597835,5.1710934 17.597825,8.8647334 v 6.648549"
|
||||
id="path3828" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g4"
|
||||
transform="matrix(0.53791749,0,0,0.53791749,5.3006475,37.927524)" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 13 KiB |
944
src/Mod/Assembly/Gui/Resources/icons/Assembly_ExportASMT.svg
Normal file
@@ -0,0 +1,944 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="48px"
|
||||
height="48px"
|
||||
id="svg4198"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="1.1-beta1 (77e7b44db3, 2021-03-28)"
|
||||
sodipodi:docname="Assembly_ExportASMT.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
||||
version="1.1"
|
||||
inkscape:export-filename="/home/yorik/Sources/FreeCAD/src/Gui/Icons/freecad-doc.png"
|
||||
inkscape:export-xdpi="128"
|
||||
inkscape:export-ydpi="128"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs4200">
|
||||
<linearGradient
|
||||
id="linearGradient15218">
|
||||
<stop
|
||||
style="stop-color:#f0f0ef;stop-opacity:1.0000000;"
|
||||
offset="0.0000000"
|
||||
id="stop15220" />
|
||||
<stop
|
||||
id="stop2269"
|
||||
offset="0.59928656"
|
||||
style="stop-color:#e8e8e8;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop2267"
|
||||
offset="0.82758623"
|
||||
style="stop-color:#ffffff;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#d8d8d3;stop-opacity:1.0000000;"
|
||||
offset="1.0000000"
|
||||
id="stop15222" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient2259">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop2261" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop2263" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient2224">
|
||||
<stop
|
||||
style="stop-color:#7c7c7c;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop2226" />
|
||||
<stop
|
||||
style="stop-color:#b8b8b8;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop2228" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2224"
|
||||
id="linearGradient2230"
|
||||
x1="35.996582"
|
||||
y1="40.458221"
|
||||
x2="33.664921"
|
||||
y2="37.770721"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(6.161836,4.033411)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient2251">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop2253" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop2255" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2251"
|
||||
id="linearGradient2257"
|
||||
x1="33.396004"
|
||||
y1="36.921333"
|
||||
x2="34.170048"
|
||||
y2="38.070381"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(6.161836,3.658411)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2259"
|
||||
id="linearGradient13651"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.999421,0,0,1,5.991319,4.033411)"
|
||||
x1="26.076092"
|
||||
y1="26.696676"
|
||||
x2="30.811172"
|
||||
y2="42.007351" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient15218"
|
||||
id="linearGradient13653"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.067236,0,0,0.989276,4.391684,4.035227)"
|
||||
x1="22.308331"
|
||||
y1="18.992140"
|
||||
x2="35.785294"
|
||||
y2="39.498238" />
|
||||
<linearGradient
|
||||
id="linearGradient3864">
|
||||
<stop
|
||||
style="stop-color:#71b2f8;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3866" />
|
||||
<stop
|
||||
style="stop-color:#002795;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop3868" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3682">
|
||||
<stop
|
||||
id="stop3684"
|
||||
offset="0"
|
||||
style="stop-color:#ff6d0f;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop3686"
|
||||
offset="1"
|
||||
style="stop-color:#ff1000;stop-opacity:1;" />
|
||||
</linearGradient>
|
||||
<inkscape:perspective
|
||||
id="perspective3148"
|
||||
inkscape:persp3d-origin="32 : 21.333333 : 1"
|
||||
inkscape:vp_z="64 : 32 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 32 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<linearGradient
|
||||
id="linearGradient3864-9">
|
||||
<stop
|
||||
id="stop3866-1"
|
||||
offset="0"
|
||||
style="stop-color:#204a87;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop3868-1"
|
||||
offset="1"
|
||||
style="stop-color:#729fcf;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3682-0">
|
||||
<stop
|
||||
style="stop-color:#a40000;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop3684-0" />
|
||||
<stop
|
||||
style="stop-color:#ef2929;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop3686-0" />
|
||||
</linearGradient>
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 32 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="64 : 32 : 1"
|
||||
inkscape:persp3d-origin="32 : 21.333333 : 1"
|
||||
id="perspective3148-5" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3682-0-6"
|
||||
id="radialGradient3817-5-3"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.2361257,0.30001695,-0.83232803,3.3883821,-499.9452,-167.33108)"
|
||||
cx="270.58316"
|
||||
cy="33.899986"
|
||||
fx="270.58316"
|
||||
fy="33.899986"
|
||||
r="19.571428" />
|
||||
<linearGradient
|
||||
id="linearGradient3682-0-6">
|
||||
<stop
|
||||
style="stop-color:#ff390f;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop3684-0-7" />
|
||||
<stop
|
||||
style="stop-color:#ff1000;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop3686-0-5" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient5060"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop5062"
|
||||
offset="0"
|
||||
style="stop-color:black;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop5064"
|
||||
offset="1"
|
||||
style="stop-color:black;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient5048">
|
||||
<stop
|
||||
id="stop5050"
|
||||
offset="0"
|
||||
style="stop-color:black;stop-opacity:0;" />
|
||||
<stop
|
||||
style="stop-color:black;stop-opacity:1;"
|
||||
offset="0.5"
|
||||
id="stop5056" />
|
||||
<stop
|
||||
id="stop5052"
|
||||
offset="1"
|
||||
style="stop-color:black;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient15662">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1.0000000;"
|
||||
offset="0.0000000"
|
||||
id="stop15664" />
|
||||
<stop
|
||||
style="stop-color:#f8f8f8;stop-opacity:1.0000000;"
|
||||
offset="1.0000000"
|
||||
id="stop15666" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
fy="64.5679"
|
||||
fx="20.8921"
|
||||
r="5.257"
|
||||
cy="64.5679"
|
||||
cx="20.8921"
|
||||
id="aigrd3">
|
||||
<stop
|
||||
id="stop15573"
|
||||
style="stop-color:#F0F0F0"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop15575"
|
||||
style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
|
||||
offset="1.0000000" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
fy="114.5684"
|
||||
fx="20.8921"
|
||||
r="5.256"
|
||||
cy="114.5684"
|
||||
cx="20.8921"
|
||||
id="aigrd2">
|
||||
<stop
|
||||
id="stop15566"
|
||||
style="stop-color:#F0F0F0"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop15568"
|
||||
style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
|
||||
offset="1.0000000" />
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
id="linearGradient269">
|
||||
<stop
|
||||
style="stop-color:#a3a3a3;stop-opacity:1.0000000;"
|
||||
offset="0.0000000"
|
||||
id="stop270" />
|
||||
<stop
|
||||
style="stop-color:#4c4c4c;stop-opacity:1.0000000;"
|
||||
offset="1.0000000"
|
||||
id="stop271" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient259">
|
||||
<stop
|
||||
style="stop-color:#fafafa;stop-opacity:1.0000000;"
|
||||
offset="0.0000000"
|
||||
id="stop260" />
|
||||
<stop
|
||||
style="stop-color:#bbbbbb;stop-opacity:1.0000000;"
|
||||
offset="1.0000000"
|
||||
id="stop261" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.000000,0.000000,0.000000,0.284916,0.000000,30.08928)"
|
||||
r="15.821514"
|
||||
fy="42.07798"
|
||||
fx="24.306795"
|
||||
cy="42.07798"
|
||||
cx="24.306795"
|
||||
id="radialGradient4548"
|
||||
xlink:href="#linearGradient5060"
|
||||
inkscape:collect="always" />
|
||||
<radialGradient
|
||||
r="29.036913"
|
||||
fy="132.28575"
|
||||
fx="61.518883"
|
||||
cy="132.28575"
|
||||
cx="61.518883"
|
||||
gradientTransform="matrix(0,-0.1143588,0.5026551,1.8070519e-7,-11.472507,114.13954)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient3023"
|
||||
xlink:href="#linearGradient2795"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
id="linearGradient2795">
|
||||
<stop
|
||||
id="stop2797"
|
||||
offset="0"
|
||||
style="stop-color:#b8b8b8;stop-opacity:0.49803922" />
|
||||
<stop
|
||||
id="stop2799"
|
||||
offset="1"
|
||||
style="stop-color:#7f7f7f;stop-opacity:0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient4671">
|
||||
<stop
|
||||
id="stop4673"
|
||||
offset="0"
|
||||
style="stop-color:#ffd43b;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop4675"
|
||||
offset="1"
|
||||
style="stop-color:#ffe873;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
y2="114.39767"
|
||||
x2="135.66525"
|
||||
y1="20.603781"
|
||||
x1="26.648937"
|
||||
gradientTransform="matrix(0.562541,0,0,0.567972,-9.399749,-5.305317)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient3030"
|
||||
xlink:href="#linearGradient4689"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
id="linearGradient4689">
|
||||
<stop
|
||||
id="stop4691"
|
||||
offset="0"
|
||||
style="stop-color:#5a9fd4;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop4693"
|
||||
offset="1"
|
||||
style="stop-color:#306998;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3878">
|
||||
<stop
|
||||
id="stop3880"
|
||||
offset="0"
|
||||
style="stop-color:#000000;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop3882"
|
||||
offset="1"
|
||||
style="stop-color:#000000;stop-opacity:1;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3839">
|
||||
<stop
|
||||
id="stop3841"
|
||||
offset="0"
|
||||
style="stop-color:#73d216;stop-opacity:0;" />
|
||||
<stop
|
||||
id="stop3843"
|
||||
offset="1"
|
||||
style="stop-color:#73d216;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
y2="30.117304"
|
||||
x2="8.6358585"
|
||||
y1="44.755539"
|
||||
x1="46.097534"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient3856"
|
||||
xlink:href="#linearGradient3839"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="39.660793"
|
||||
x2="47.374119"
|
||||
y1="39.660793"
|
||||
x1="9.78771"
|
||||
id="linearGradient3864-91"
|
||||
xlink:href="#linearGradient3878"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="34.257634"
|
||||
x2="46.865743"
|
||||
y1="34.257634"
|
||||
x1="9.923306"
|
||||
id="linearGradient3876"
|
||||
xlink:href="#linearGradient3878"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="48.904176"
|
||||
x2="29.433661"
|
||||
y1="48.904176"
|
||||
x1="28.433661"
|
||||
id="linearGradient3884"
|
||||
xlink:href="#linearGradient3878"
|
||||
inkscape:collect="always" />
|
||||
<radialGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.96428571,-4.7464116e-8,3.0929704e-8,0.24561404,1.1607127,39.228072)"
|
||||
r="28.5"
|
||||
fy="52"
|
||||
fx="32.5"
|
||||
cy="52"
|
||||
cx="32.5"
|
||||
id="radialGradient3893"
|
||||
xlink:href="#linearGradient3887"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
id="linearGradient3887"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop3889"
|
||||
offset="0"
|
||||
style="stop-color:#2e3436;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop3891"
|
||||
offset="1"
|
||||
style="stop-color:#2e3436;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="15"
|
||||
x2="20"
|
||||
y1="35"
|
||||
x1="22"
|
||||
id="linearGradient3869"
|
||||
xlink:href="#linearGradient3863"
|
||||
inkscape:collect="always"
|
||||
gradientTransform="matrix(0.8782269,0,0,0.88301047,5.1375817,6.0680873)" />
|
||||
<linearGradient
|
||||
id="linearGradient3863"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop3865"
|
||||
offset="0"
|
||||
style="stop-color:#271903;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop3867"
|
||||
offset="1"
|
||||
style="stop-color:#c17d11;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="17"
|
||||
x2="38"
|
||||
y1="27"
|
||||
x1="39"
|
||||
id="linearGradient3879"
|
||||
xlink:href="#linearGradient3873"
|
||||
inkscape:collect="always"
|
||||
gradientTransform="matrix(0.8782269,0,0,0.88301047,5.1375817,6.0680873)" />
|
||||
<linearGradient
|
||||
id="linearGradient3873"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop3875"
|
||||
offset="0"
|
||||
style="stop-color:#8f5902;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop3877"
|
||||
offset="1"
|
||||
style="stop-color:#e9b96e;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3813"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop3815"
|
||||
offset="0"
|
||||
style="stop-color:#e9b96e;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop3817"
|
||||
offset="1"
|
||||
style="stop-color:#c17d11;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="33.772724"
|
||||
x2="23.272728"
|
||||
y1="24.545454"
|
||||
x1="22.181818"
|
||||
id="linearGradient3833"
|
||||
xlink:href="#linearGradient3813"
|
||||
inkscape:collect="always"
|
||||
gradientTransform="matrix(0.8782269,0,0,0.88301047,5.1375817,6.0680873)" />
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="24.636364"
|
||||
x2="45.5"
|
||||
y1="32.5"
|
||||
x1="51.5"
|
||||
id="linearGradient3859"
|
||||
xlink:href="#linearGradient3853"
|
||||
inkscape:collect="always"
|
||||
gradientTransform="matrix(0.8782269,0,0,0.88301047,5.1375817,6.0680873)" />
|
||||
<linearGradient
|
||||
id="linearGradient3853"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop3855"
|
||||
offset="0"
|
||||
style="stop-color:#8f5902;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop3857"
|
||||
offset="1"
|
||||
style="stop-color:#c17d11;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 32 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="64 : 32 : 1"
|
||||
inkscape:persp3d-origin="32 : 21.333333 : 1"
|
||||
id="perspective3148-3" />
|
||||
<inkscape:perspective
|
||||
id="perspective3148-5-6"
|
||||
inkscape:persp3d-origin="32 : 21.333333 : 1"
|
||||
inkscape:vp_z="64 : 32 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 32 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<radialGradient
|
||||
r="19.571428"
|
||||
fy="33.899986"
|
||||
fx="270.58316"
|
||||
cy="33.899986"
|
||||
cx="270.58316"
|
||||
gradientTransform="matrix(1.2361257,0.30001695,-0.83232803,3.3883821,-499.9452,-167.33108)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient3817-5-3-1"
|
||||
xlink:href="#linearGradient3682-0-6"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
y2="43.559998"
|
||||
x2="41.689651"
|
||||
y1="21.799999"
|
||||
x1="35.482758"
|
||||
gradientTransform="matrix(1.4500001,0,0,1.4705882,-161.57497,-8.808822)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient3012"
|
||||
xlink:href="#linearGradient3071"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
id="linearGradient3071">
|
||||
<stop
|
||||
id="stop3073"
|
||||
offset="0"
|
||||
style="stop-color:#fce94f;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop3075"
|
||||
offset="1"
|
||||
style="stop-color:#c4a000;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient69056"
|
||||
x1="27.243999"
|
||||
x2="22.243999"
|
||||
y1="54.588001"
|
||||
y2="40.588001"
|
||||
gradientTransform="translate(-1.2435,-2.5881)"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#c4a000"
|
||||
offset="0"
|
||||
id="stop14" />
|
||||
<stop
|
||||
stop-color="#fce94f"
|
||||
offset="1"
|
||||
id="stop16" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient4399"
|
||||
x1="48.714001"
|
||||
x2="44.714001"
|
||||
y1="45.585999"
|
||||
y2="34.585999"
|
||||
gradientTransform="translate(1.2856,1.4142)"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#c4a000"
|
||||
offset="0"
|
||||
id="stop8" />
|
||||
<stop
|
||||
stop-color="#edd400"
|
||||
offset="1"
|
||||
id="stop10" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient69709"
|
||||
x1="20.243999"
|
||||
x2="17.243999"
|
||||
y1="37.588001"
|
||||
y2="27.587999"
|
||||
gradientTransform="matrix(1,-0.026667,0,1,81.696,-5.3735)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient4383" />
|
||||
<linearGradient
|
||||
id="linearGradient4383">
|
||||
<stop
|
||||
stop-color="#3465a4"
|
||||
offset="0"
|
||||
id="stop2" />
|
||||
<stop
|
||||
stop-color="#729fcf"
|
||||
offset="1"
|
||||
id="stop4" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient69717"
|
||||
x1="50.714001"
|
||||
x2="48.714001"
|
||||
y1="25.586"
|
||||
y2="20.586"
|
||||
gradientTransform="translate(61.2256,1.0356)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient4383" />
|
||||
<linearGradient
|
||||
id="linearGradient4389"
|
||||
x1="20.243999"
|
||||
x2="17.243999"
|
||||
y1="37.588001"
|
||||
y2="27.587999"
|
||||
gradientTransform="translate(-1.2435,-2.5881)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3774" />
|
||||
<linearGradient
|
||||
id="linearGradient3774">
|
||||
<stop
|
||||
stop-color="#4e9a06"
|
||||
offset="0"
|
||||
id="stop21" />
|
||||
<stop
|
||||
stop-color="#8ae234"
|
||||
offset="1"
|
||||
id="stop23" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient69042"
|
||||
x1="48.714001"
|
||||
x2="44.714001"
|
||||
y1="45.585999"
|
||||
y2="34.585999"
|
||||
gradientTransform="translate(-12.714,-17.586)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3774" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient69056"
|
||||
id="linearGradient920"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-41.2435,-2.5881)"
|
||||
x1="20.243999"
|
||||
y1="37.588001"
|
||||
x2="17.243999"
|
||||
y2="27.587999" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4399"
|
||||
id="linearGradient922"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-52.714,-17.586)"
|
||||
x1="48.714001"
|
||||
y1="45.585999"
|
||||
x2="44.714001"
|
||||
y2="34.585999" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient69056"
|
||||
id="linearGradient949"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-1.2435,-2.5881)"
|
||||
x1="27.243999"
|
||||
y1="54.588001"
|
||||
x2="22.243999"
|
||||
y2="40.588001" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4399"
|
||||
id="linearGradient951"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(1.2856,1.4142)"
|
||||
x1="48.714001"
|
||||
y1="45.585999"
|
||||
x2="44.714001"
|
||||
y2="34.585999" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#bebebe"
|
||||
borderopacity="1.0000000"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="2.0000001"
|
||||
inkscape:cx="41.249999"
|
||||
inkscape:cy="-38.249999"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:window-width="1482"
|
||||
inkscape:window-height="1013"
|
||||
inkscape:window-x="1492"
|
||||
inkscape:window-y="215"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:window-maximized="0"
|
||||
objecttolerance="10.0"
|
||||
gridtolerance="10.0"
|
||||
guidetolerance="10.0"
|
||||
inkscape:pagecheckerboard="0" />
|
||||
<metadata
|
||||
id="metadata4203">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:date>2005-10-15</dc:date>
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>Andreas Nilsson</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>edit</rdf:li>
|
||||
<rdf:li>copy</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
|
||||
<dc:contributor>
|
||||
<cc:Agent>
|
||||
<dc:title>Jakub Steiner</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:contributor>
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
|
||||
<cc:permits
|
||||
rdf:resource="http://web.resource.org/cc/Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://web.resource.org/cc/Distribution" />
|
||||
<cc:requires
|
||||
rdf:resource="http://web.resource.org/cc/Notice" />
|
||||
<cc:requires
|
||||
rdf:resource="http://web.resource.org/cc/Attribution" />
|
||||
<cc:permits
|
||||
rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
|
||||
<cc:requires
|
||||
rdf:resource="http://web.resource.org/cc/ShareAlike" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer">
|
||||
<g
|
||||
id="g12863"
|
||||
transform="matrix(1.0624766,0,0,1.0624766,-5.9998602,-8.9998192)">
|
||||
<path
|
||||
style="fill:url(#linearGradient13653);fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 15.072946,10.500852 h 29.856385 c 0.31574,0 0.569926,0.253093 0.569926,0.567472 v 27.167362 c 0,2.476452 -6.87981,8.303087 -9.267932,8.303087 H 15.072946 c -0.31574,0 -0.569926,-0.253092 -0.569926,-0.567473 V 11.068324 c 0,-0.314379 0.254186,-0.567472 0.569926,-0.567472 z"
|
||||
id="rect12413"
|
||||
sodipodi:nodetypes="ccccccccc"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
ry="0.0000000"
|
||||
rx="0.0000000"
|
||||
y="11.5"
|
||||
x="15.502951"
|
||||
height="34.040764"
|
||||
width="28.997349"
|
||||
id="rect15244"
|
||||
style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient13651);stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
id="path2210"
|
||||
d="m 36.220918,46.536966 c 2.030418,0.329898 9.588793,-4.529929 9.284411,-8.497844 -1.563262,2.423097 -4.758522,1.286738 -8.86728,1.445748 0,0 0.395369,6.552096 -0.417131,7.052096 z"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#linearGradient2230);fill-opacity:1;fill-rule:evenodd;stroke:#868a84;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.369318;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient2257);stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
|
||||
d="m 37.671355,44.345464 c 1.369779,-0.683829 4.428249,-2.146465 5.72763,-4.027469 -1.596094,0.680055 -2.94781,0.209496 -5.702334,0.190405 0,0 0.162322,3.062094 -0.0253,3.837064 z"
|
||||
id="path2247"
|
||||
sodipodi:nodetypes="cccc"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="g1124"
|
||||
transform="matrix(0.51097675,0,0,0.51097675,4.6676716,33.094635)">
|
||||
<g
|
||||
id="g40"
|
||||
style="stroke-width:2"
|
||||
transform="translate(9.249999,-58.229485)">
|
||||
<path
|
||||
d="M 9,49 V 35 l 28,10 v 14 z"
|
||||
id="path30"
|
||||
style="fill:url(#linearGradient949);stroke:#302b00;stroke-linejoin:round"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="M 37,59 V 45 L 55,28 v 13 z"
|
||||
id="path32"
|
||||
style="fill:url(#linearGradient951);stroke:#302b00;stroke-linejoin:round"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="M 11.008,47.606 11,37.9997 l 24,8 0.0081,10.185 z"
|
||||
id="path34"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:none;stroke:#fce94f" />
|
||||
<path
|
||||
d="M 39.005,54.168 39,45.9998 l 14,-13 0.0021,7.1768 z"
|
||||
id="path36"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:none;stroke:#edd400" />
|
||||
<path
|
||||
d="M 23,40 42,23 55,28 37,45 Z"
|
||||
id="path38"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#fce94f;stroke:#302b00;stroke-linejoin:round" />
|
||||
</g>
|
||||
<g
|
||||
id="g943"
|
||||
transform="translate(-50.750001,-58.229485)">
|
||||
<path
|
||||
d="m 91,33.5 -0.02739,-14.214 12.967,4.3352 v 14.5 z"
|
||||
id="path54"
|
||||
style="fill:url(#linearGradient69709);stroke:#0b1521;stroke-width:2;stroke-linejoin:round"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 92.927,32.029 0.04731,-10.141 8.9272,3.29 0.0781,10.042 z"
|
||||
id="path56"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:none;stroke:#729fcf;stroke-width:2" />
|
||||
<path
|
||||
d="m 103.94,38.121 v -14.5 l 11,-9 L 115,28 Z"
|
||||
id="path58"
|
||||
style="fill:url(#linearGradient69717);stroke:#0b1521;stroke-width:2;stroke-linejoin:round"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 105.94,33.621 v -9 l 7,-6 -0.0122,8.5816 z"
|
||||
id="path60"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:none;stroke:#729fcf;stroke-width:2" />
|
||||
<path
|
||||
d="M 90.973,19.286 102,9.9998 l 12.94,4.6214 -11,9 z"
|
||||
id="path62"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#729fcf;stroke:#0b1521;stroke-width:2;stroke-linejoin:round" />
|
||||
</g>
|
||||
<g
|
||||
id="g963"
|
||||
transform="translate(49.249999,-58.229485)">
|
||||
<g
|
||||
style="stroke:#172a04;stroke-width:2;stroke-linejoin:round"
|
||||
transform="translate(-40)"
|
||||
id="g48">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:url(#linearGradient4389)"
|
||||
id="path42"
|
||||
d="M 9,35 V 21 l 14,5 v 14 z" />
|
||||
<path
|
||||
style="fill:#8ae234"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path44"
|
||||
d="M 9,21 28.585,5.209 42,10.0001 l -19,16 z" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:url(#linearGradient69042)"
|
||||
id="path46"
|
||||
d="M 23,40 V 26 l 7.9726,-6.7138 0.02739,13.714 z" />
|
||||
</g>
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:url(#linearGradient920);fill-opacity:1;stroke:#172a04;stroke-width:2;stroke-linejoin:round"
|
||||
id="path912"
|
||||
d="M -31,35 V 21 l 14,5 v 14 z" />
|
||||
<path
|
||||
style="fill:#fce94f;fill-opacity:1;stroke:#172a04;stroke-width:2;stroke-linejoin:round;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path914"
|
||||
d="M -31,21 -11.415,5.209 2,10.0001 l -19,16 z" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:url(#linearGradient922);fill-opacity:1;stroke:#172a04;stroke-width:2;stroke-linejoin:round"
|
||||
id="path916"
|
||||
d="M -17,40 V 26 l 7.9726,-6.7138 0.02739,13.714 z" />
|
||||
<path
|
||||
d="m -15,36 v -9 l 4,-3.5 v 9 z"
|
||||
id="path50"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:none;stroke:#fce94f;stroke-width:2;stroke-opacity:1" />
|
||||
<path
|
||||
d="m -29.049,33.746 0.08695,-9.9796 9.9568,3.5229 -0.02105,9.9613 z"
|
||||
id="path52"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:none;stroke:#fce94f;stroke-width:2;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g1226"
|
||||
transform="matrix(0.5473089,0,0,0.58505616,73.011656,9.917284)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="display:inline;fill:url(#linearGradient3012);fill-opacity:1;fill-rule:evenodd;stroke:#302b00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m -105.12497,13.249999 v 14 h -26 v 22 h 26 v 14 l 32,-25.000001 z"
|
||||
id="path3343"
|
||||
sodipodi:nodetypes="cccccccc"
|
||||
inkscape:export-filename="/home/yorik/Documents/Lab/Draft/icons/changeprop.png"
|
||||
inkscape:export-xdpi="4.1683898"
|
||||
inkscape:export-ydpi="4.1683898" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="display:inline;fill:none;stroke:#fce94f;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m -103.12497,17.370879 v 11.87912 h -26 v 18 h 26 V 59.195054 L -76.124976,38.42206 Z"
|
||||
id="path3343-2"
|
||||
sodipodi:nodetypes="cccccccc"
|
||||
inkscape:export-filename="/home/yorik/Documents/Lab/Draft/icons/changeprop.png"
|
||||
inkscape:export-xdpi="4.1683898"
|
||||
inkscape:export-ydpi="4.1683898" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 30 KiB |
615
src/Mod/Assembly/Gui/Resources/icons/Assembly_SolveAssembly.svg
Normal file
@@ -0,0 +1,615 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="64"
|
||||
height="64"
|
||||
id="svg3559"
|
||||
version="1.1"
|
||||
inkscape:version="1.1-beta1 (77e7b44db3, 2021-03-28)"
|
||||
sodipodi:docname="Assembly_SolveAssembly.svg"
|
||||
viewBox="0 0 64 64"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs3561">
|
||||
<linearGradient
|
||||
id="linearGradient4383-3"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop73188"
|
||||
offset="0"
|
||||
style="stop-color:#3465a4;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop73190"
|
||||
offset="1"
|
||||
style="stop-color:#729fcf;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4383-3"
|
||||
id="linearGradient4389-0"
|
||||
x1="27.243532"
|
||||
y1="54.588112"
|
||||
x2="21.243532"
|
||||
y2="30.588112"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-1.243533,-2.588112)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4393-9"
|
||||
id="linearGradient4399-7"
|
||||
x1="48.714352"
|
||||
y1="45.585785"
|
||||
x2="40.714352"
|
||||
y2="24.585787"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(1.2856487,1.4142136)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient4393-9">
|
||||
<stop
|
||||
style="stop-color:#204a87;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop4395-8" />
|
||||
<stop
|
||||
style="stop-color:#3465a4;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop4397-1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient3774">
|
||||
<stop
|
||||
style="stop-color:#4e9a06;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop3776" />
|
||||
<stop
|
||||
style="stop-color:#8ae234;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop3778" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient8662"
|
||||
id="radialGradient1503"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(2.0414388,0,0,0.73027218,-169.35231,-78.023792)"
|
||||
cx="24.837126"
|
||||
cy="36.421127"
|
||||
fx="24.837126"
|
||||
fy="36.421127"
|
||||
r="15.644737" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient8662">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop8664" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop8666" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2831"
|
||||
id="linearGradient1486"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.370336,0,0,1.3589114,0.02150968,-18.214919)"
|
||||
x1="13.478554"
|
||||
y1="10.612206"
|
||||
x2="15.419417"
|
||||
y2="19.115122" />
|
||||
<linearGradient
|
||||
id="linearGradient2831">
|
||||
<stop
|
||||
style="stop-color:#3465a4;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop2833" />
|
||||
<stop
|
||||
id="stop2855"
|
||||
offset="0.33333334"
|
||||
style="stop-color:#5b86be;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#83a8d8;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop2835" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2847"
|
||||
id="linearGradient1488"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-1.370336,0,0,-1.3589114,64.512944,44.464873)"
|
||||
x1="37.128052"
|
||||
y1="29.729605"
|
||||
x2="37.065414"
|
||||
y2="26.194071" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient2847">
|
||||
<stop
|
||||
style="stop-color:#3465a4;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop2849" />
|
||||
<stop
|
||||
style="stop-color:#3465a4;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop2851" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3063"
|
||||
id="linearGradient3858"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="42.703487"
|
||||
y1="20.547306"
|
||||
x2="26.605606"
|
||||
y2="33.634254" />
|
||||
<linearGradient
|
||||
id="linearGradient3063">
|
||||
<stop
|
||||
id="stop3065"
|
||||
offset="0"
|
||||
style="stop-color:#729fcf;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop3067"
|
||||
offset="1"
|
||||
style="stop-color:#204a87;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2380"
|
||||
id="linearGradient3034"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="41.791897"
|
||||
y1="20.134634"
|
||||
x2="23.705669"
|
||||
y2="34.083359" />
|
||||
<linearGradient
|
||||
id="linearGradient2380">
|
||||
<stop
|
||||
style="stop-color:#729fcf;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop2382" />
|
||||
<stop
|
||||
style="stop-color:#3465a4;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop2384" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2380"
|
||||
id="linearGradient3034-4"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="26.221533"
|
||||
y1="31.125586"
|
||||
x2="46.731483"
|
||||
y2="21.766298" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2831"
|
||||
id="linearGradient962"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.370336,0,0,1.3589114,0.02150968,-18.214919)"
|
||||
x1="13.478554"
|
||||
y1="10.612206"
|
||||
x2="15.419417"
|
||||
y2="19.115122" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2847"
|
||||
id="linearGradient964"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-1.370336,0,0,-1.3589114,64.512944,44.464873)"
|
||||
x1="37.128052"
|
||||
y1="29.729605"
|
||||
x2="37.065414"
|
||||
y2="26.194071" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3063"
|
||||
id="linearGradient966"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="42.703487"
|
||||
y1="20.547306"
|
||||
x2="26.605606"
|
||||
y2="33.634254" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient69056-7"
|
||||
id="linearGradient949"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-1.2435,-2.5881)"
|
||||
x1="27.243999"
|
||||
y1="54.588001"
|
||||
x2="22.243999"
|
||||
y2="40.588001" />
|
||||
<linearGradient
|
||||
id="linearGradient69056-7"
|
||||
x1="27.243999"
|
||||
x2="22.243999"
|
||||
y1="54.588001"
|
||||
y2="40.588001"
|
||||
gradientTransform="translate(-1.2435,-2.5881)"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#c4a000"
|
||||
offset="0"
|
||||
id="stop14" />
|
||||
<stop
|
||||
stop-color="#fce94f"
|
||||
offset="1"
|
||||
id="stop16" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4399-70"
|
||||
id="linearGradient951"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(1.2856,1.4142)"
|
||||
x1="48.714001"
|
||||
y1="45.585999"
|
||||
x2="44.714001"
|
||||
y2="34.585999" />
|
||||
<linearGradient
|
||||
id="linearGradient4399-70"
|
||||
x1="48.714001"
|
||||
x2="44.714001"
|
||||
y1="45.585999"
|
||||
y2="34.585999"
|
||||
gradientTransform="translate(1.2856,1.4142)"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#c4a000"
|
||||
offset="0"
|
||||
id="stop8" />
|
||||
<stop
|
||||
stop-color="#edd400"
|
||||
offset="1"
|
||||
id="stop10" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient69709"
|
||||
x1="20.243999"
|
||||
x2="17.243999"
|
||||
y1="37.588001"
|
||||
y2="27.587999"
|
||||
gradientTransform="matrix(1,-0.026667,0,1,81.696,-5.3735)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient4383-3" />
|
||||
<linearGradient
|
||||
id="linearGradient69717"
|
||||
x1="50.714001"
|
||||
x2="48.714001"
|
||||
y1="25.586"
|
||||
y2="20.586"
|
||||
gradientTransform="translate(61.2256,1.0356)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient4383-3" />
|
||||
<linearGradient
|
||||
id="linearGradient4389-9"
|
||||
x1="20.243999"
|
||||
x2="17.243999"
|
||||
y1="37.588001"
|
||||
y2="27.587999"
|
||||
gradientTransform="translate(-1.2435,-2.5881)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3774" />
|
||||
<linearGradient
|
||||
id="linearGradient69042-0"
|
||||
x1="48.714001"
|
||||
x2="44.714001"
|
||||
y1="45.585999"
|
||||
y2="34.585999"
|
||||
gradientTransform="translate(-12.714,-17.586)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3774" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient69056-7"
|
||||
id="linearGradient920"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-41.2435,-2.5881)"
|
||||
x1="20.243999"
|
||||
y1="37.588001"
|
||||
x2="17.243999"
|
||||
y2="27.587999" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4399-70"
|
||||
id="linearGradient922"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-52.714,-17.586)"
|
||||
x1="48.714001"
|
||||
y1="45.585999"
|
||||
x2="44.714001"
|
||||
y2="34.585999" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5"
|
||||
inkscape:cx="44.1"
|
||||
inkscape:cy="9.1"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:window-width="3686"
|
||||
inkscape:window-height="1571"
|
||||
inkscape:window-x="145"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-global="true"
|
||||
objecttolerance="10.0"
|
||||
gridtolerance="10.0"
|
||||
guidetolerance="10.0"
|
||||
inkscape:pagecheckerboard="0">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3007"
|
||||
empspacing="4"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true"
|
||||
spacingx="0.5"
|
||||
spacingy="0.5" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata3564">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>Path-Stock</dc:title>
|
||||
<dc:date>2015-07-04</dc:date>
|
||||
<dc:relation>https://www.freecadweb.org/wiki/index.php?title=Artwork</dc:relation>
|
||||
<dc:publisher>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:publisher>
|
||||
<dc:identifier>FreeCAD/src/Mod/Path/Gui/Resources/icons/Path-Stock.svg</dc:identifier>
|
||||
<dc:rights>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD LGPL2+</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:rights>
|
||||
<cc:license>https://www.gnu.org/copyleft/lesser.html</cc:license>
|
||||
<dc:contributor>
|
||||
<cc:Agent>
|
||||
<dc:title>[agryson] Alexander Gryson</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:contributor>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer">
|
||||
<g
|
||||
id="g1124"
|
||||
transform="matrix(1.0964113,0,0,1.0964113,-13.226965,60.643745)">
|
||||
<g
|
||||
id="g40"
|
||||
style="stroke-width:2"
|
||||
transform="translate(9.249999,-58.229485)">
|
||||
<path
|
||||
d="M 9,49 V 35 l 28,10 v 14 z"
|
||||
id="path30"
|
||||
style="fill:url(#linearGradient949);stroke:#302b00;stroke-linejoin:round"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="M 37,59 V 45 L 55,28 v 13 z"
|
||||
id="path32"
|
||||
style="fill:url(#linearGradient951);stroke:#302b00;stroke-linejoin:round"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="M 11.008,47.606 11,37.9997 l 24,8 0.0081,10.185 z"
|
||||
id="path34"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:none;stroke:#fce94f" />
|
||||
<path
|
||||
d="M 39.005,54.168 39,45.9998 l 14,-13 0.0021,7.1768 z"
|
||||
id="path36"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:none;stroke:#edd400" />
|
||||
<path
|
||||
d="M 23,40 42,23 55,28 37,45 Z"
|
||||
id="path38"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#fce94f;stroke:#302b00;stroke-linejoin:round" />
|
||||
</g>
|
||||
<g
|
||||
id="g943"
|
||||
transform="translate(-50.750001,-58.229485)">
|
||||
<path
|
||||
d="m 91,33.5 -0.02739,-14.214 12.967,4.3352 v 14.5 z"
|
||||
id="path54"
|
||||
style="fill:url(#linearGradient69709);stroke:#0b1521;stroke-width:2;stroke-linejoin:round"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 92.927,32.029 0.04731,-10.141 8.9272,3.29 0.0781,10.042 z"
|
||||
id="path56"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:none;stroke:#729fcf;stroke-width:2" />
|
||||
<path
|
||||
d="m 103.94,38.121 v -14.5 l 11,-9 L 115,28 Z"
|
||||
id="path58"
|
||||
style="fill:url(#linearGradient69717);stroke:#0b1521;stroke-width:2;stroke-linejoin:round"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 105.94,33.621 v -9 l 7,-6 -0.0122,8.5816 z"
|
||||
id="path60"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:none;stroke:#729fcf;stroke-width:2" />
|
||||
<path
|
||||
d="M 90.973,19.286 102,9.9998 l 12.94,4.6214 -11,9 z"
|
||||
id="path62"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#729fcf;stroke:#0b1521;stroke-width:2;stroke-linejoin:round" />
|
||||
</g>
|
||||
<g
|
||||
id="g963"
|
||||
transform="translate(49.249999,-58.229485)">
|
||||
<g
|
||||
style="stroke:#172a04;stroke-width:2;stroke-linejoin:round"
|
||||
transform="translate(-40)"
|
||||
id="g48">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:url(#linearGradient4389-9)"
|
||||
id="path42"
|
||||
d="M 9,35 V 21 l 14,5 v 14 z" />
|
||||
<path
|
||||
style="fill:#8ae234"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path44"
|
||||
d="M 9,21 28.585,5.209 42,10.0001 l -19,16 z" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:url(#linearGradient69042-0)"
|
||||
id="path46"
|
||||
d="M 23,40 V 26 l 7.9726,-6.7138 0.02739,13.714 z" />
|
||||
</g>
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:url(#linearGradient920);fill-opacity:1;stroke:#172a04;stroke-width:2;stroke-linejoin:round"
|
||||
id="path912"
|
||||
d="M -31,35 V 21 l 14,5 v 14 z" />
|
||||
<path
|
||||
style="fill:#fce94f;fill-opacity:1;stroke:#172a04;stroke-width:2;stroke-linejoin:round;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path914"
|
||||
d="M -31,21 -11.415,5.209 2,10.0001 l -19,16 z" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:url(#linearGradient922);fill-opacity:1;stroke:#172a04;stroke-width:2;stroke-linejoin:round"
|
||||
id="path916"
|
||||
d="M -17,40 V 26 l 7.9726,-6.7138 0.02739,13.714 z" />
|
||||
<path
|
||||
d="m -15,36 v -9 l 4,-3.5 v 9 z"
|
||||
id="path50"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:none;stroke:#fce94f;stroke-width:2;stroke-opacity:1" />
|
||||
<path
|
||||
d="m -29.049,33.746 0.08695,-9.9796 9.9568,3.5229 -0.02105,9.9613 z"
|
||||
id="path52"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:none;stroke:#fce94f;stroke-width:2;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g1230"
|
||||
transform="matrix(0.82819734,0,0,0.82819734,-66.264643,5.39994)">
|
||||
<ellipse
|
||||
transform="scale(-1)"
|
||||
id="path8660"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.383333;fill:url(#radialGradient1503);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.66662;marker:none"
|
||||
inkscape:r_cx="true"
|
||||
inkscape:r_cy="true"
|
||||
cx="-118.64883"
|
||||
cy="-51.426441"
|
||||
rx="31.937773"
|
||||
ry="11.424921" />
|
||||
<g
|
||||
id="g3863"
|
||||
transform="translate(85.809699,15.628782)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc"
|
||||
inkscape:r_cy="true"
|
||||
inkscape:r_cx="true"
|
||||
id="path2865"
|
||||
d="m 27,-3.6915582 c 0,0 -12.247378,-0.8493196 -8.478954,13.4192502 H 7.986588 c 0,0 0.685168,-16.137073 19.013412,-13.4192502 z"
|
||||
style="color:#000000;display:block;overflow:visible;visibility:visible;fill:url(#linearGradient962);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient964);stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none" />
|
||||
<g
|
||||
style="fill:url(#linearGradient966);fill-opacity:1;stroke:#204a87;stroke-width:0.732809;stroke-opacity:1"
|
||||
inkscape:r_cy="true"
|
||||
inkscape:r_cx="true"
|
||||
transform="matrix(-0.79349441,-0.66481753,-0.67040672,0.78687903,77.66003,0.94046451)"
|
||||
id="g1878">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
inkscape:r_cy="true"
|
||||
inkscape:r_cx="true"
|
||||
style="color:#000000;display:block;overflow:visible;visibility:visible;fill:url(#linearGradient3034);fill-opacity:1;fill-rule:nonzero;stroke:#204a87;stroke-width:1.9334;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
|
||||
d="M 44.306783,50.229694 C 62.821497,35.818859 49.664587,13.411704 22.462411,12.49765 L 22.113843,3.1515478 7.6245439,20.496754 22.714328,33.219189 c 0,0 -0.251917,-9.88122 -0.251917,-9.88122 18.82976,0.998977 32.981627,14.071729 21.844372,26.891725 z"
|
||||
id="path1880"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
</g>
|
||||
<g
|
||||
id="g2805"
|
||||
transform="matrix(-0.69686517,-0.58385766,-0.58876622,0.69105539,72.350404,1.0127423)"
|
||||
inkscape:r_cx="true"
|
||||
inkscape:r_cy="true"
|
||||
style="fill:none;stroke:#729fcf;stroke-width:0.732809;stroke-opacity:1">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccc"
|
||||
id="path2807"
|
||||
d="M 52.368857,42.344789 C 57.336994,33.465615 49.176003,12.601866 19.05552,12.672851 L 18.677956,5.6633463 7.4378077,19.282655 19.129354,29.167094 18.807724,20.554957 c 18.244937,0.381972 33.804002,9.457851 33.561133,21.789832 z"
|
||||
style="color:#000000;display:block;overflow:visible;visibility:visible;fill:none;stroke:#729fcf;stroke-width:2.20149;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:21;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
|
||||
inkscape:r_cx="true"
|
||||
inkscape:r_cy="true" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="rotate(180,75.898143,22.314391)"
|
||||
id="g3863-0">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc"
|
||||
inkscape:r_cy="true"
|
||||
inkscape:r_cx="true"
|
||||
id="path2865-3"
|
||||
d="m 27,-3.6915582 c 0,0 -12.247378,-0.8493196 -8.478954,13.4192502 H 7.986588 c 0,0 0.685168,-16.137073 19.013412,-13.4192502 z"
|
||||
style="color:#000000;display:block;overflow:visible;visibility:visible;fill:url(#linearGradient1486);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient1488);stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none" />
|
||||
<g
|
||||
style="fill:url(#linearGradient3858);fill-opacity:1;stroke:#204a87;stroke-width:0.732809;stroke-opacity:1"
|
||||
inkscape:r_cy="true"
|
||||
inkscape:r_cx="true"
|
||||
transform="matrix(-0.79349441,-0.66481753,-0.67040672,0.78687903,77.66003,0.94046451)"
|
||||
id="g1878-6">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
inkscape:r_cy="true"
|
||||
inkscape:r_cx="true"
|
||||
style="color:#000000;display:block;overflow:visible;visibility:visible;fill:url(#linearGradient3034-4);fill-opacity:1;fill-rule:nonzero;stroke:#204a87;stroke-width:1.9334;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
|
||||
d="M 44.306783,50.229694 C 62.821497,35.818859 49.664587,13.411704 22.462411,12.49765 L 22.113843,3.1515478 7.6245439,20.496754 22.714328,33.219189 c 0,0 -0.251917,-9.88122 -0.251917,-9.88122 18.82976,0.998977 32.981627,14.071729 21.844372,26.891725 z"
|
||||
id="path1880-2"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
</g>
|
||||
<g
|
||||
id="g2805-4"
|
||||
transform="matrix(-0.69686517,-0.58385766,-0.58876622,0.69105539,72.350404,1.0127423)"
|
||||
inkscape:r_cx="true"
|
||||
inkscape:r_cy="true"
|
||||
style="fill:none;stroke:#729fcf;stroke-width:0.732809;stroke-opacity:1">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccc"
|
||||
id="path2807-5"
|
||||
d="M 52.368857,42.344789 C 57.864671,33.591679 49.176003,12.601866 19.05552,12.672851 L 18.677956,5.6633463 7.4378077,19.282655 19.129354,29.167094 18.807724,20.554957 c 18.244937,0.381972 33.804002,9.457851 33.561133,21.789832 z"
|
||||
style="color:#000000;display:block;overflow:visible;visibility:visible;fill:none;stroke:#729fcf;stroke-width:2.20149;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:21;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
|
||||
inkscape:r_cx="true"
|
||||
inkscape:r_cy="true" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 22 KiB |
441
src/Mod/Assembly/Gui/Resources/icons/Assembly_ToggleGrounded.svg
Normal file
@@ -0,0 +1,441 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="64px"
|
||||
height="64px"
|
||||
id="svg2821"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
sodipodi:docname="Assembly_FixObject.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
||||
version="1.1"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs2823">
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3377"
|
||||
id="radialGradient3701"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
cx="84.883324"
|
||||
cy="77.042847"
|
||||
fx="84.883324"
|
||||
fy="77.042847"
|
||||
r="19.467436"
|
||||
gradientTransform="matrix(2.8492421,1.2585119,-0.4040415,0.9147407,-125.84131,-100.25805)" />
|
||||
<linearGradient
|
||||
id="linearGradient3377">
|
||||
<stop
|
||||
id="stop3379"
|
||||
offset="0"
|
||||
style="stop-color:#faff2b;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop3381"
|
||||
offset="1"
|
||||
style="stop-color:#ffaa00;stop-opacity:1;" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3377"
|
||||
id="radialGradient3699"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
cx="76.383331"
|
||||
cy="94.369568"
|
||||
fx="76.383331"
|
||||
fy="94.369568"
|
||||
r="19.467436"
|
||||
gradientTransform="matrix(0.9818943,0.1894295,-0.4109427,2.1300924,40.163453,-121.11559)" />
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 32 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="64 : 32 : 1"
|
||||
inkscape:persp3d-origin="32 : 21.333333 : 1"
|
||||
id="perspective2829" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3801-5"
|
||||
id="linearGradient3807-7"
|
||||
x1="110"
|
||||
y1="35"
|
||||
x2="85"
|
||||
y2="35"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
spreadMethod="reflect"
|
||||
gradientTransform="translate(-62,-16)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient3801-5">
|
||||
<stop
|
||||
style="stop-color:#c4a000;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop3803-3" />
|
||||
<stop
|
||||
style="stop-color:#fce94f;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop3805-5" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
xlink:href="#linearGradient3838"
|
||||
id="linearGradient3844"
|
||||
x1="2802.9631"
|
||||
y1="538.36249"
|
||||
x2="2859.7263"
|
||||
y2="786.05646"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
id="linearGradient3838">
|
||||
<stop
|
||||
style="stop-color:#34e0e2;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop3840" />
|
||||
<stop
|
||||
style="stop-color:#06989a;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop3842" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
y2="786.05646"
|
||||
x2="2859.7263"
|
||||
y1="538.36249"
|
||||
x1="2802.9631"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient3045"
|
||||
xlink:href="#linearGradient3172" />
|
||||
<linearGradient
|
||||
id="linearGradient3172">
|
||||
<stop
|
||||
id="stop3174"
|
||||
offset="0"
|
||||
style="stop-color:#ef2929;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop3176"
|
||||
offset="1"
|
||||
style="stop-color:#a40000;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
xlink:href="#linearGradient3172"
|
||||
id="linearGradient3880"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="2802.9631"
|
||||
y1="626.0874"
|
||||
x2="2849.4058"
|
||||
y2="822.17853" />
|
||||
<linearGradient
|
||||
id="linearGradient69056"
|
||||
x1="27.243999"
|
||||
x2="22.243999"
|
||||
y1="54.588001"
|
||||
y2="40.588001"
|
||||
gradientTransform="translate(-1.2435,-2.5881)"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#c4a000"
|
||||
offset="0"
|
||||
id="stop14" />
|
||||
<stop
|
||||
stop-color="#fce94f"
|
||||
offset="1"
|
||||
id="stop16" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient4399"
|
||||
x1="48.714001"
|
||||
x2="44.714001"
|
||||
y1="45.585999"
|
||||
y2="34.585999"
|
||||
gradientTransform="translate(1.2856,1.4142)"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#c4a000"
|
||||
offset="0"
|
||||
id="stop8" />
|
||||
<stop
|
||||
stop-color="#edd400"
|
||||
offset="1"
|
||||
id="stop10" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient69056"
|
||||
id="linearGradient920"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-1.2435,-2.5881)"
|
||||
x1="20.243999"
|
||||
y1="37.588001"
|
||||
x2="17.243999"
|
||||
y2="27.587999" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient73208"
|
||||
id="linearGradient69042-3"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-12.714351,-17.585786)"
|
||||
x1="48.714352"
|
||||
y1="45.585785"
|
||||
x2="46.714352"
|
||||
y2="35.585785" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient73208">
|
||||
<stop
|
||||
style="stop-color:#c4a000;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop73210" />
|
||||
<stop
|
||||
style="stop-color:#edd400;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop73212" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient69056"
|
||||
id="linearGradient3"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-1.2435,-2.5881)"
|
||||
x1="27.243999"
|
||||
y1="54.588001"
|
||||
x2="22.243999"
|
||||
y2="40.588001" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="7.2080075"
|
||||
inkscape:cx="39.469992"
|
||||
inkscape:cy="21.226393"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1356"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2992"
|
||||
empspacing="2"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true"
|
||||
originx="0"
|
||||
originy="0"
|
||||
spacingy="1"
|
||||
spacingx="1"
|
||||
units="px" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata2826">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>[wmayer]</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:title>Part_Cylinder</dc:title>
|
||||
<dc:date>2011-10-10</dc:date>
|
||||
<dc:relation>http://www.freecad.org/wiki/index.php?title=Artwork</dc:relation>
|
||||
<dc:publisher>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:publisher>
|
||||
<dc:identifier>FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Cylinder.svg</dc:identifier>
|
||||
<dc:rights>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD LGPL2+</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:rights>
|
||||
<cc:license>https://www.gnu.org/copyleft/lesser.html</cc:license>
|
||||
<dc:contributor>
|
||||
<cc:Agent>
|
||||
<dc:title>[agryson] Alexander Gryson</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:contributor>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer">
|
||||
<g
|
||||
id="g1"
|
||||
transform="matrix(1.0462449,0,0,1.0462449,-1.4798357,-1.6196228)">
|
||||
<g
|
||||
id="g40"
|
||||
style="stroke-width:2">
|
||||
<path
|
||||
d="M 9,49 V 35 l 28,10 v 14 z"
|
||||
id="path30"
|
||||
style="fill:url(#linearGradient3);stroke:#302b00;stroke-linejoin:round"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="M 37,59 V 45 L 55,28 v 13 z"
|
||||
id="path32"
|
||||
style="fill:url(#linearGradient4399);stroke:#302b00;stroke-linejoin:round"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="M 11.008,47.606 11,37.9997 l 24,8 0.0081,10.185 z"
|
||||
id="path34"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:none;stroke:#fce94f" />
|
||||
<path
|
||||
d="M 39.005,54.168 39,45.9998 l 14,-13 0.0021,7.1768 z"
|
||||
id="path36"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:none;stroke:#edd400" />
|
||||
<path
|
||||
d="M 23,40 42,23 55,28 37,45 Z"
|
||||
id="path38"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#fce94f;stroke:#302b00;stroke-linejoin:round" />
|
||||
</g>
|
||||
<g
|
||||
display="none"
|
||||
fill="#ef2929"
|
||||
fill-rule="evenodd"
|
||||
opacity="0.588"
|
||||
stroke="#ef2929"
|
||||
stroke-width="1px"
|
||||
id="g94">
|
||||
<path
|
||||
d="M 9,35 V 49"
|
||||
id="path66" />
|
||||
<path
|
||||
d="M 9,35 37,45"
|
||||
id="path68" />
|
||||
<path
|
||||
d="M 55,28 V 41"
|
||||
id="path70" />
|
||||
<path
|
||||
d="M 37,45 55,28"
|
||||
id="path72" />
|
||||
<path
|
||||
d="M 23,40 V 26"
|
||||
id="path74" />
|
||||
<path
|
||||
d="m 29,5 13,5"
|
||||
id="path76" />
|
||||
<path
|
||||
d="M 23,26 42,10"
|
||||
id="path78" />
|
||||
<path
|
||||
d="M 19,13 29,5"
|
||||
id="path80" />
|
||||
<path
|
||||
d="m 55,15 -9,8"
|
||||
id="path82" />
|
||||
<path
|
||||
d="M 42,23 V 10"
|
||||
id="path84" />
|
||||
<path
|
||||
d="m 42,23 14,5"
|
||||
id="path86" />
|
||||
<path
|
||||
d="M 23,40 42,23"
|
||||
id="path88" />
|
||||
<path
|
||||
d="M 23,10 H 42"
|
||||
id="path90" />
|
||||
<path
|
||||
d="M 34,17 V 30"
|
||||
id="path92" />
|
||||
</g>
|
||||
<path
|
||||
d="M 9,35 V 21 l 14,5 v 14 z"
|
||||
id="path912"
|
||||
style="fill:url(#linearGradient920);fill-opacity:1;stroke:#172a04;stroke-width:2;stroke-linejoin:round"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="M 9,21 28.585,5.209 42,10.0001 l -19,16 z"
|
||||
id="path914"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#fce94f;fill-opacity:1;stroke:#172a04;stroke-width:2;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;stroke:#fce94f;stroke-width:2;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path52"
|
||||
d="m 10.951,33.746 0.08695,-9.9796 9.9568,3.5229 -0.02105,9.9613 z" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path69040"
|
||||
d="M 23,40 V 26 L 42,10 v 13 z"
|
||||
style="fill:url(#linearGradient69042-3);fill-opacity:1;fill-rule:nonzero;stroke:#302b00;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path69044-6"
|
||||
d="M 25,36 V 27 L 40,14 v 8 z"
|
||||
style="fill:none;stroke:#edd400;stroke-width:2;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.58573981,0,0,1.0757034,6.6270859,6.5711408)"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:54.2152px;font-family:Arial;-inkscape-font-specification:Arial;display:inline;overflow:visible;visibility:visible;fill:#ff2600;fill-opacity:1;fill-rule:nonzero;stroke:#731200;stroke-width:2.51469;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
id="text3796"
|
||||
inkscape:label="Lock">
|
||||
<g
|
||||
transform="matrix(0.26232603,0,0,0.14315619,-698.74089,-70.421371)"
|
||||
id="g2385"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:54.2152px;font-family:Arial;-inkscape-font-specification:Arial;display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient3844);fill-opacity:1;fill-rule:nonzero;stroke:#042a2a;stroke-width:11.8436;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate">
|
||||
<path
|
||||
style="display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient3045);fill-opacity:1;fill-rule:evenodd;stroke:#280000;stroke-width:11.8436;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m 2751.3603,595.12568 v 0 0 l -2e-4,46.44262 h 30.9618 l -0.2475,-47.17448 h -0.05 c 0.2977,-25.0696 20.9388,-45.71077 46.7403,-45.71077 25.8014,0 46.4426,20.64117 46.4421,46.44263 v 0 46.44262 h 30.9618 v -46.44262 0 c 5e-4,-41.28234 -25.801,-77.40438 -77.4039,-77.40438 -51.6029,0 -77.4044,36.12204 -77.4044,77.40438 z"
|
||||
id="path2387" />
|
||||
<rect
|
||||
style="display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient3880);fill-opacity:1;fill-rule:evenodd;stroke:#280000;stroke-width:11.8436;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
id="rect2389"
|
||||
width="196.09097"
|
||||
height="154.80875"
|
||||
x="2730.7192"
|
||||
y="641.5683" />
|
||||
<rect
|
||||
style="display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ef2929;stroke-width:11.8436;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
id="rect2389-0"
|
||||
width="175.44977"
|
||||
height="134.16759"
|
||||
x="2741.0398"
|
||||
y="651.88885" />
|
||||
</g>
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:54.2152px;font-family:Arial;-inkscape-font-specification:Arial;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#a40000;stroke-width:4.59028;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="M 25.719895,26.594196 H 60.915549"
|
||||
id="path3777-7" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:54.2152px;font-family:Arial;-inkscape-font-specification:Arial;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#a40000;stroke-width:4.59028;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="M 25.719921,32.504016 H 60.915574"
|
||||
id="path3777-3-5" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:54.2152px;font-family:Arial;-inkscape-font-specification:Arial;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#a40000;stroke-width:4.59028;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="M 25.719921,38.413838 H 60.915574"
|
||||
id="path3777-6-3" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:54.2152px;font-family:Arial;-inkscape-font-specification:Arial;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ef2929;stroke-width:2.29514;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m 25.719879,20.684376 v -6.648549 c 1.2e-5,-3.69364 5.414701,-8.8647326 17.597824,-8.8647334 12.183122,-7e-7 17.597835,5.1710934 17.597825,8.8647334 v 6.648549"
|
||||
id="path3828" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 17 KiB |
@@ -20,8 +20,107 @@
|
||||
<item row="1" column="0">
|
||||
<widget class="QListWidget" name="featureList"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<layout class="QHBoxLayout" name="hLayoutDistance">
|
||||
<item>
|
||||
<widget class="QLabel" name="distanceLabel">
|
||||
<property name="text">
|
||||
<string>Distance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::QuantitySpinBox" name="distanceSpinbox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true">mm</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<layout class="QHBoxLayout" name="hLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="offsetLabel">
|
||||
<property name="text">
|
||||
<string>Offset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::QuantitySpinBox" name="offsetSpinbox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true">mm</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<layout class="QHBoxLayout" name="hLayoutRotation">
|
||||
<item>
|
||||
<widget class="QLabel" name="rotationLabel">
|
||||
<property name="text">
|
||||
<string>Rotation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::QuantitySpinBox" name="rotationSpinbox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true">deg</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QToolButton" name="PushButtonReverse">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Reverse the direction of the joint.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Reverse</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="Resources/resource.qrc">
|
||||
<normaloff>:/icons/button_sort.svg</normaloff>:/icons/button_sort.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Gui::QuantitySpinBox</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>Gui/QuantitySpinBox.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@@ -42,8 +42,34 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="Gui::PrefCheckBox" name="CheckBox_InsertInParts">
|
||||
<property name="toolTip">
|
||||
<string>If checked, the selected object will be inserted inside a Part container, unless it is already a Part.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Insert as part</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>InsertInParts</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Assembly</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Gui::PrefCheckBox</class>
|
||||
<extends>QCheckBox</extends>
|
||||
<header>Gui/PrefWidgets.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@@ -13,8 +13,57 @@
|
||||
<property name="windowTitle">
|
||||
<string>General</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="verticalLayout_1">
|
||||
<item row="0" column="0">
|
||||
<widget class="Gui::PrefCheckBox" name="checkBoxEnableEscape">
|
||||
<property name="toolTip">
|
||||
<string>Allow to leave edit mode when pressing Esc button</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Esc leave edit mode</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>LeaveEditWithEscape</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Assembly</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="groundFirstPartLabel">
|
||||
<property name="text">
|
||||
<string>Ground first part:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="groundFirstPart">
|
||||
<property name="toolTip">
|
||||
<string>When you insert the first part in the assembly, you can choose to ground the part automatically.</string>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<spacer name="horSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
@@ -29,6 +78,13 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Gui::PrefCheckBox</class>
|
||||
<extends>QCheckBox</extends>
|
||||
<header>Gui/PrefWidgets.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
739
src/Mod/Assembly/Gui/ViewProviderAssembly.cpp
Normal file
@@ -0,0 +1,739 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#ifndef _PreComp_
|
||||
#include <QMessageBox>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <Inventor/events/SoKeyboardEvent.h>
|
||||
#endif
|
||||
|
||||
#include <App/Link.h>
|
||||
#include <App/Document.h>
|
||||
#include <App/DocumentObject.h>
|
||||
#include <App/Part.h>
|
||||
#include <Gui/Application.h>
|
||||
#include <Gui/BitmapFactory.h>
|
||||
#include <Gui/CommandT.h>
|
||||
#include <Gui/MDIView.h>
|
||||
#include <Gui/View3DInventor.h>
|
||||
#include <Gui/View3DInventorViewer.h>
|
||||
#include <Mod/Assembly/App/AssemblyObject.h>
|
||||
#include <Mod/Assembly/App/AssemblyUtils.h>
|
||||
#include <Mod/Assembly/App/JointGroup.h>
|
||||
#include <Mod/PartDesign/App/Body.h>
|
||||
|
||||
#include "ViewProviderAssembly.h"
|
||||
#include "ViewProviderAssemblyPy.h"
|
||||
|
||||
|
||||
using namespace Assembly;
|
||||
using namespace AssemblyGui;
|
||||
|
||||
void printPlacement(Base::Placement plc, const char* name)
|
||||
{
|
||||
Base::Vector3d pos = plc.getPosition();
|
||||
Base::Vector3d axis;
|
||||
double angle;
|
||||
Base::Rotation rot = plc.getRotation();
|
||||
rot.getRawValue(axis, angle);
|
||||
Base::Console().Warning(
|
||||
"placement %s : position (%.1f, %.1f, %.1f) - axis (%.1f, %.1f, %.1f) angle %.1f\n",
|
||||
name,
|
||||
pos.x,
|
||||
pos.y,
|
||||
pos.z,
|
||||
axis.x,
|
||||
axis.y,
|
||||
axis.z,
|
||||
angle);
|
||||
}
|
||||
|
||||
PROPERTY_SOURCE(AssemblyGui::ViewProviderAssembly, Gui::ViewProviderPart)
|
||||
|
||||
ViewProviderAssembly::ViewProviderAssembly()
|
||||
: SelectionObserver(true)
|
||||
, dragMode(DragMode::None)
|
||||
, canStartDragging(false)
|
||||
, partMoving(false)
|
||||
, enableMovement(true)
|
||||
, jointVisibilityBackup(false)
|
||||
, docsToMove({})
|
||||
{}
|
||||
|
||||
ViewProviderAssembly::~ViewProviderAssembly() = default;
|
||||
|
||||
QIcon ViewProviderAssembly::getIcon() const
|
||||
{
|
||||
return Gui::BitmapFactory().pixmap("Geoassembly.svg");
|
||||
}
|
||||
|
||||
bool ViewProviderAssembly::doubleClicked()
|
||||
{
|
||||
if (isInEditMode()) {
|
||||
// Part is already 'Active' so we exit edit mode.
|
||||
// Gui::Command::doCommand(Gui::Command::Gui, "Gui.activeDocument().resetEdit()");
|
||||
Gui::Application::Instance->activeDocument()->resetEdit();
|
||||
}
|
||||
else {
|
||||
// assure the Assembly workbench
|
||||
if (App::GetApplication()
|
||||
.GetUserParameter()
|
||||
.GetGroup("BaseApp")
|
||||
->GetGroup("Preferences")
|
||||
->GetGroup("Mod/Assembly")
|
||||
->GetBool("SwitchToWB", true)) {
|
||||
Gui::Command::assureWorkbench("AssemblyWorkbench");
|
||||
}
|
||||
|
||||
// Part is not 'Active' so we enter edit mode to make it so.
|
||||
Gui::Application::Instance->activeDocument()->setEdit(this);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ViewProviderAssembly::canDragObject(App::DocumentObject* obj) const
|
||||
{
|
||||
// The user should not be able to drag the joint group out of the assembly
|
||||
if (!obj || obj->getTypeId() == Assembly::JointGroup::getClassTypeId()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// else if a solid is removed, remove associated joints if any.
|
||||
bool prompted = false;
|
||||
auto* assemblyPart = static_cast<AssemblyObject*>(getObject());
|
||||
std::vector<App::DocumentObject*> joints = assemblyPart->getJoints();
|
||||
|
||||
// Combine the joints and groundedJoints vectors into one for simplicity.
|
||||
std::vector<App::DocumentObject*> allJoints = assemblyPart->getJoints();
|
||||
std::vector<App::DocumentObject*> groundedJoints = assemblyPart->getGroundedJoints();
|
||||
allJoints.insert(allJoints.end(), groundedJoints.begin(), groundedJoints.end());
|
||||
|
||||
Gui::Command::openCommand(tr("Delete associated joints").toStdString().c_str());
|
||||
for (auto joint : allJoints) {
|
||||
// getLinkObjFromProp returns nullptr if the property doesn't exist.
|
||||
App::DocumentObject* obj1 = AssemblyObject::getObjFromNameProp(joint, "Object1", "Part1");
|
||||
App::DocumentObject* obj2 = AssemblyObject::getObjFromNameProp(joint, "Object2", "Part2");
|
||||
App::DocumentObject* part1 = AssemblyObject::getLinkObjFromProp(joint, "Part1");
|
||||
App::DocumentObject* part2 = AssemblyObject::getLinkObjFromProp(joint, "Part2");
|
||||
App::DocumentObject* obj3 = AssemblyObject::getLinkObjFromProp(joint, "ObjectToGround");
|
||||
if (obj == obj1 || obj == obj2 || obj == part1 || obj == part2 || obj == obj3) {
|
||||
if (!prompted) {
|
||||
prompted = true;
|
||||
QMessageBox msgBox;
|
||||
msgBox.setText(tr("The object is associated to one or more joints."));
|
||||
msgBox.setInformativeText(
|
||||
tr("Do you want to move the object and delete associated joints?"));
|
||||
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
msgBox.setDefaultButton(QMessageBox::No);
|
||||
int ret = msgBox.exec();
|
||||
|
||||
if (ret == QMessageBox::No) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Gui::Command::doCommand(Gui::Command::Gui,
|
||||
"App.activeDocument().removeObject('%s')",
|
||||
joint->getNameInDocument());
|
||||
}
|
||||
}
|
||||
Gui::Command::commitCommand();
|
||||
|
||||
// Remove grounded tag if any. (as it is not done in jointObject.py onDelete)
|
||||
std::string label = obj->Label.getValue();
|
||||
|
||||
if (label.size() >= 4 && label.substr(label.size() - 2) == " 🔒") {
|
||||
label = label.substr(0, label.size() - 2);
|
||||
obj->Label.setValue(label.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ViewProviderAssembly::setEdit(int ModNum)
|
||||
{
|
||||
// Set the part as 'Activated' ie bold in the tree.
|
||||
Gui::Command::doCommand(Gui::Command::Gui,
|
||||
"Gui.ActiveDocument.ActiveView.setActiveObject('%s', "
|
||||
"App.getDocument('%s').getObject('%s'))",
|
||||
PARTKEY,
|
||||
this->getObject()->getDocument()->getName(),
|
||||
this->getObject()->getNameInDocument());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ViewProviderAssembly::unsetEdit(int ModNum)
|
||||
{
|
||||
Q_UNUSED(ModNum);
|
||||
canStartDragging = false;
|
||||
partMoving = false;
|
||||
docsToMove = {};
|
||||
|
||||
// Check if the view is still active before trying to deactivate the assembly.
|
||||
auto doc = getDocument();
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
auto activeView = doc->getActiveView();
|
||||
if (!activeView) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the part as not 'Activated' ie not bold in the tree.
|
||||
Gui::Command::doCommand(Gui::Command::Gui,
|
||||
"appDoc = App.getDocument('%s')\n"
|
||||
"Gui.getDocument(appDoc).ActiveView.setActiveObject('%s', None)",
|
||||
this->getObject()->getDocument()->getName(),
|
||||
PARTKEY);
|
||||
}
|
||||
|
||||
bool ViewProviderAssembly::isInEditMode() const
|
||||
{
|
||||
App::DocumentObject* activePart = getActivePart();
|
||||
if (!activePart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return activePart == this->getObject();
|
||||
}
|
||||
|
||||
App::DocumentObject* ViewProviderAssembly::getActivePart() const
|
||||
{
|
||||
auto activeDoc = Gui::Application::Instance->activeDocument();
|
||||
if (!activeDoc) {
|
||||
activeDoc = getDocument();
|
||||
}
|
||||
auto activeView = activeDoc->getActiveView();
|
||||
if (!activeView) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return activeView->getActiveObject<App::DocumentObject*>(PARTKEY);
|
||||
}
|
||||
|
||||
bool ViewProviderAssembly::keyPressed(bool pressed, int key)
|
||||
{
|
||||
if (key == SoKeyboardEvent::ESCAPE) {
|
||||
if (isInEditMode()) {
|
||||
|
||||
ParameterGrp::handle hPgr = App::GetApplication().GetParameterGroupByPath(
|
||||
"User parameter:BaseApp/Preferences/Mod/Assembly");
|
||||
|
||||
return !hPgr->GetBool("LeaveEditWithEscape", true);
|
||||
}
|
||||
}
|
||||
|
||||
return false; // handle all other key events
|
||||
}
|
||||
|
||||
bool ViewProviderAssembly::mouseMove(const SbVec2s& cursorPos, Gui::View3DInventorViewer* viewer)
|
||||
{
|
||||
// Initialize or end the dragging of parts
|
||||
if (canStartDragging) {
|
||||
canStartDragging = false;
|
||||
|
||||
if (enableMovement && getSelectedObjectsWithinAssembly()) {
|
||||
dragMode = findDragMode();
|
||||
|
||||
if (dragMode == DragMode::None) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SbVec3f vec;
|
||||
if (dragMode == DragMode::RotationOnPlane
|
||||
|| dragMode == DragMode::TranslationOnAxisAndRotationOnePlane) {
|
||||
vec = viewer->getPointOnXYPlaneOfPlacement(cursorPos, jcsGlobalPlc);
|
||||
initialPositionRot = Base::Vector3d(vec[0], vec[1], vec[2]);
|
||||
}
|
||||
|
||||
if (dragMode == DragMode::TranslationOnAxis
|
||||
|| dragMode == DragMode::TranslationOnAxisAndRotationOnePlane) {
|
||||
Base::Vector3d zAxis =
|
||||
jcsGlobalPlc.getRotation().multVec(Base::Vector3d(0., 0., 1.));
|
||||
Base::Vector3d pos = jcsGlobalPlc.getPosition();
|
||||
SbVec3f axisCenter(pos.x, pos.y, pos.z);
|
||||
SbVec3f axis(zAxis.x, zAxis.y, zAxis.z);
|
||||
vec = viewer->getPointOnLine(cursorPos, axisCenter, axis);
|
||||
initialPosition = Base::Vector3d(vec[0], vec[1], vec[2]);
|
||||
}
|
||||
else if (dragMode != DragMode::RotationOnPlane) {
|
||||
vec = viewer->getPointOnFocalPlane(cursorPos);
|
||||
initialPosition = Base::Vector3d(vec[0], vec[1], vec[2]);
|
||||
prevPosition = initialPosition;
|
||||
}
|
||||
|
||||
initMove();
|
||||
}
|
||||
}
|
||||
|
||||
// Do the dragging of parts
|
||||
if (partMoving) {
|
||||
Base::Vector3d newPos, newPosRot;
|
||||
if (dragMode == DragMode::RotationOnPlane
|
||||
|| dragMode == DragMode::TranslationOnAxisAndRotationOnePlane) {
|
||||
SbVec3f vec = viewer->getPointOnXYPlaneOfPlacement(cursorPos, jcsGlobalPlc);
|
||||
newPosRot = Base::Vector3d(vec[0], vec[1], vec[2]);
|
||||
}
|
||||
|
||||
if (dragMode == DragMode::TranslationOnAxis
|
||||
|| dragMode == DragMode::TranslationOnAxisAndRotationOnePlane) {
|
||||
Base::Vector3d zAxis = jcsGlobalPlc.getRotation().multVec(Base::Vector3d(0., 0., 1.));
|
||||
Base::Vector3d pos = jcsGlobalPlc.getPosition();
|
||||
SbVec3f axisCenter(pos.x, pos.y, pos.z);
|
||||
SbVec3f axis(zAxis.x, zAxis.y, zAxis.z);
|
||||
SbVec3f vec = viewer->getPointOnLine(cursorPos, axisCenter, axis);
|
||||
newPos = Base::Vector3d(vec[0], vec[1], vec[2]);
|
||||
}
|
||||
else if (dragMode != DragMode::RotationOnPlane) {
|
||||
SbVec3f vec = viewer->getPointOnFocalPlane(cursorPos);
|
||||
newPos = Base::Vector3d(vec[0], vec[1], vec[2]);
|
||||
}
|
||||
|
||||
|
||||
for (auto& pair : docsToMove) {
|
||||
App::DocumentObject* obj = pair.first;
|
||||
auto* propPlacement =
|
||||
dynamic_cast<App::PropertyPlacement*>(obj->getPropertyByName("Placement"));
|
||||
if (propPlacement) {
|
||||
Base::Placement plc = pair.second;
|
||||
// Base::Console().Warning("newPos %f %f %f\n", newPos.x, newPos.y, newPos.z);
|
||||
|
||||
if (dragMode == DragMode::RotationOnPlane) {
|
||||
Base::Vector3d center = jcsGlobalPlc.getPosition();
|
||||
Base::Vector3d norm =
|
||||
jcsGlobalPlc.getRotation().multVec(Base::Vector3d(0., 0., -1.));
|
||||
double angle =
|
||||
(newPosRot - center).GetAngleOriented(initialPositionRot - center, norm);
|
||||
// Base::Console().Warning("angle %f\n", angle);
|
||||
Base::Rotation zRotation = Base::Rotation(Base::Vector3d(0., 0., 1.), angle);
|
||||
Base::Placement rotatedGlovalJcsPlc =
|
||||
jcsGlobalPlc * Base::Placement(Base::Vector3d(), zRotation);
|
||||
Base::Placement jcsPlcRelativeToPart = plc.inverse() * jcsGlobalPlc;
|
||||
plc = rotatedGlovalJcsPlc * jcsPlcRelativeToPart.inverse();
|
||||
}
|
||||
else if (dragMode == DragMode::TranslationOnAxis) {
|
||||
Base::Vector3d pos = plc.getPosition() + (newPos - initialPosition);
|
||||
plc.setPosition(pos);
|
||||
}
|
||||
else if (dragMode == DragMode::TranslationOnAxisAndRotationOnePlane) {
|
||||
Base::Vector3d pos = plc.getPosition() + (newPos - initialPosition);
|
||||
plc.setPosition(pos);
|
||||
|
||||
Base::Placement newJcsGlobalPlc = jcsGlobalPlc;
|
||||
newJcsGlobalPlc.setPosition(jcsGlobalPlc.getPosition()
|
||||
+ (newPos - initialPosition));
|
||||
|
||||
Base::Vector3d center = newJcsGlobalPlc.getPosition();
|
||||
Base::Vector3d norm =
|
||||
newJcsGlobalPlc.getRotation().multVec(Base::Vector3d(0., 0., -1.));
|
||||
|
||||
Base::Vector3d projInitialPositionRot =
|
||||
initialPositionRot.ProjectToPlane(newJcsGlobalPlc.getPosition(), norm);
|
||||
double angle =
|
||||
(newPosRot - center).GetAngleOriented(initialPositionRot - center, norm);
|
||||
// Base::Console().Warning("angle %f\n", angle);
|
||||
Base::Rotation zRotation = Base::Rotation(Base::Vector3d(0., 0., 1.), angle);
|
||||
Base::Placement rotatedGlovalJcsPlc =
|
||||
newJcsGlobalPlc * Base::Placement(Base::Vector3d(), zRotation);
|
||||
Base::Placement jcsPlcRelativeToPart = plc.inverse() * newJcsGlobalPlc;
|
||||
plc = rotatedGlovalJcsPlc * jcsPlcRelativeToPart.inverse();
|
||||
}
|
||||
else { // DragMode::Translation
|
||||
Base::Vector3d delta = newPos - prevPosition;
|
||||
prevPosition = newPos;
|
||||
|
||||
Base::Vector3d pos = propPlacement->getValue().getPosition() + delta;
|
||||
// Base::Vector3d pos = newPos + (plc.getPosition() - initialPosition);
|
||||
plc.setPosition(pos);
|
||||
}
|
||||
propPlacement->setValue(plc);
|
||||
}
|
||||
}
|
||||
|
||||
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
||||
"User parameter:BaseApp/Preferences/Mod/Assembly");
|
||||
bool solveOnMove = hGrp->GetBool("SolveOnMove", true);
|
||||
if (solveOnMove) {
|
||||
auto* assemblyPart = static_cast<AssemblyObject*>(getObject());
|
||||
assemblyPart->solve();
|
||||
// assemblyPart->doDragStep();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ViewProviderAssembly::mouseButtonPressed(int Button,
|
||||
bool pressed,
|
||||
const SbVec2s& cursorPos,
|
||||
const Gui::View3DInventorViewer* viewer)
|
||||
{
|
||||
// Left Mouse button ****************************************************
|
||||
if (Button == 1) {
|
||||
if (pressed) {
|
||||
canStartDragging = true;
|
||||
}
|
||||
else { // Button 1 released
|
||||
// release event is not received when user click on a part for selection.
|
||||
// So we use SelectionObserver to know if something got selected.
|
||||
|
||||
canStartDragging = false;
|
||||
if (partMoving) {
|
||||
endMove();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ViewProviderAssembly::getSelectedObjectsWithinAssembly()
|
||||
{
|
||||
// check the current selection, and check if any of the selected objects are within this
|
||||
// App::Part
|
||||
// If any, put them into the vector docsToMove and return true.
|
||||
// Get the document
|
||||
Gui::Document* doc = Gui::Application::Instance->activeDocument();
|
||||
|
||||
if (!doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the assembly object for this ViewProvider
|
||||
AssemblyObject* assemblyPart = static_cast<AssemblyObject*>(getObject());
|
||||
|
||||
if (!assemblyPart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto& selObj : Gui::Selection().getSelectionEx("",
|
||||
App::DocumentObject::getClassTypeId(),
|
||||
Gui::ResolveMode::NoResolve)) {
|
||||
// getSubNames() returns ["Body001.Pad.Face14", "Body002.Pad.Face7"]
|
||||
// if you have several objects within the same assembly selected.
|
||||
|
||||
std::vector<std::string> objsSubNames = selObj.getSubNames();
|
||||
for (auto& subNamesStr : objsSubNames) {
|
||||
std::vector<std::string> subNames = parseSubNames(subNamesStr);
|
||||
|
||||
App::DocumentObject* obj = getObjectFromSubNames(subNames);
|
||||
if (!obj) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the selected object is a child of the assembly
|
||||
if (assemblyPart->hasObject(obj, true)) {
|
||||
auto* propPlacement =
|
||||
dynamic_cast<App::PropertyPlacement*>(obj->getPropertyByName("Placement"));
|
||||
if (propPlacement) {
|
||||
docsToMove.emplace_back(obj, propPlacement->getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function is called before the selection is updated. So if a user click and drag a part
|
||||
// it is not selected at that point. So we need to get the preselection too.
|
||||
if (Gui::Selection().hasPreselection()) {
|
||||
|
||||
// Base::Console().Warning("Gui::Selection().getPreselection().pSubName %s\n",
|
||||
// Gui::Selection().getPreselection().pSubName);
|
||||
|
||||
std::string subNamesStr = Gui::Selection().getPreselection().pSubName;
|
||||
std::vector<std::string> subNames = parseSubNames(subNamesStr);
|
||||
|
||||
App::DocumentObject* preselectedObj = getObjectFromSubNames(subNames);
|
||||
if (preselectedObj && assemblyPart->hasObject(preselectedObj, true)) {
|
||||
bool alreadyIn = false;
|
||||
for (auto& pair : docsToMove) {
|
||||
App::DocumentObject* obj = pair.first;
|
||||
if (obj == preselectedObj) {
|
||||
alreadyIn = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!alreadyIn) {
|
||||
auto* propPlacement = dynamic_cast<App::PropertyPlacement*>(
|
||||
preselectedObj->getPropertyByName("Placement"));
|
||||
if (propPlacement) {
|
||||
docsToMove.emplace_back(preselectedObj, propPlacement->getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !docsToMove.empty();
|
||||
}
|
||||
|
||||
std::vector<std::string> ViewProviderAssembly::parseSubNames(std::string& subNamesStr)
|
||||
{
|
||||
std::vector<std::string> subNames;
|
||||
std::string subName;
|
||||
std::istringstream subNameStream(subNamesStr);
|
||||
while (std::getline(subNameStream, subName, '.')) {
|
||||
subNames.push_back(subName);
|
||||
}
|
||||
return subNames;
|
||||
}
|
||||
|
||||
App::DocumentObject* ViewProviderAssembly::getObjectFromSubNames(std::vector<std::string>& subNames)
|
||||
{
|
||||
App::Document* appDoc = App::GetApplication().getActiveDocument();
|
||||
|
||||
std::string objName;
|
||||
if (subNames.size() < 2) {
|
||||
return nullptr;
|
||||
}
|
||||
else if (subNames.size() == 2) {
|
||||
// If two subnames then it can't be a body and the object we want is the first one
|
||||
// For example we want box in "box.face1"
|
||||
return appDoc->getObject(subNames[0].c_str());
|
||||
}
|
||||
|
||||
// From here subnames is at least 3 and can be more. There are several cases to consider :
|
||||
// bodyOrLink.pad.face1 -> bodyOrLink should be the moving entity
|
||||
// partOrLink.bodyOrLink.pad.face1 -> partOrLink should be the moving entity
|
||||
// partOrLink.box.face1 -> partOrLink should be the moving entity
|
||||
// partOrLink1...ParOrLinkn.bodyOrLink.pad.face1 -> partOrLink1 should be the moving entity
|
||||
// assembly1.partOrLink1...ParOrLinkn.bodyOrLink.pad.face1 -> partOrLink1 should be the moving
|
||||
// entity assembly1.boxOrLink1.face1 -> boxOrLink1 should be the moving entity
|
||||
|
||||
for (auto objName : subNames) {
|
||||
App::DocumentObject* obj = appDoc->getObject(objName.c_str());
|
||||
if (!obj) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (obj->getTypeId().isDerivedFrom(AssemblyObject::getClassTypeId())) {
|
||||
continue;
|
||||
}
|
||||
else if (obj->getTypeId().isDerivedFrom(App::Part::getClassTypeId())
|
||||
|| obj->getTypeId().isDerivedFrom(PartDesign::Body::getClassTypeId())) {
|
||||
return obj;
|
||||
}
|
||||
else if (obj->getTypeId().isDerivedFrom(App::Link::getClassTypeId())) {
|
||||
App::Link* link = dynamic_cast<App::Link*>(obj);
|
||||
|
||||
App::DocumentObject* linkedObj = link->getLinkedObject(true);
|
||||
if (!linkedObj) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (linkedObj->getTypeId().isDerivedFrom(App::Part::getClassTypeId())
|
||||
|| linkedObj->getTypeId().isDerivedFrom(PartDesign::Body::getClassTypeId())) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// then its neither a part or body or a link to a part or body. So it is something like
|
||||
// assembly.box.face1
|
||||
objName = subNames[subNames.size() - 2];
|
||||
return appDoc->getObject(objName.c_str());
|
||||
}
|
||||
|
||||
ViewProviderAssembly::DragMode ViewProviderAssembly::findDragMode()
|
||||
{
|
||||
if (docsToMove.size() == 1) {
|
||||
auto* assemblyPart = static_cast<AssemblyObject*>(getObject());
|
||||
std::string partPropName;
|
||||
movingJoint =
|
||||
assemblyPart->getJointOfPartConnectingToGround(docsToMove[0].first, partPropName);
|
||||
|
||||
if (!movingJoint) {
|
||||
return DragMode::Translation;
|
||||
}
|
||||
|
||||
JointType jointType = AssemblyObject::getJointType(movingJoint);
|
||||
if (jointType == JointType::Fixed) {
|
||||
// If fixed joint we need to find the upstream joint to find move mode.
|
||||
// For example : Gnd -(revolute)- A -(fixed)- B : if user try to move B, then we should
|
||||
// actually move A
|
||||
App::DocumentObject* upstreamPart =
|
||||
assemblyPart->getUpstreamMovingPart(docsToMove[0].first);
|
||||
docsToMove.clear();
|
||||
if (!upstreamPart) {
|
||||
return DragMode::None;
|
||||
}
|
||||
|
||||
auto* propPlacement =
|
||||
dynamic_cast<App::PropertyPlacement*>(upstreamPart->getPropertyByName("Placement"));
|
||||
if (propPlacement) {
|
||||
docsToMove.emplace_back(upstreamPart, propPlacement->getValue());
|
||||
}
|
||||
|
||||
movingJoint =
|
||||
assemblyPart->getJointOfPartConnectingToGround(docsToMove[0].first, partPropName);
|
||||
if (!movingJoint) {
|
||||
return DragMode::Translation;
|
||||
}
|
||||
jointType = AssemblyObject::getJointType(movingJoint);
|
||||
}
|
||||
|
||||
const char* plcPropName = (partPropName == "Part1") ? "Placement1" : "Placement2";
|
||||
const char* objPropName = (partPropName == "Part1") ? "Object1" : "Object2";
|
||||
|
||||
// jcsPlc is relative to the Object
|
||||
jcsPlc = AssemblyObject::getPlacementFromProp(movingJoint, plcPropName);
|
||||
|
||||
// Make jcsGlobalPlc relative to the origin of the doc
|
||||
Base::Placement global_plc =
|
||||
AssemblyObject::getGlobalPlacement(movingJoint, objPropName, partPropName.c_str());
|
||||
jcsGlobalPlc = global_plc * jcsPlc;
|
||||
|
||||
// Add downstream parts so that they move together
|
||||
auto downstreamParts = assemblyPart->getDownstreamParts(docsToMove[0].first, movingJoint);
|
||||
for (auto part : downstreamParts) {
|
||||
auto* propPlacement =
|
||||
dynamic_cast<App::PropertyPlacement*>(part->getPropertyByName("Placement"));
|
||||
if (propPlacement) {
|
||||
docsToMove.emplace_back(part, propPlacement->getValue());
|
||||
}
|
||||
}
|
||||
|
||||
jointVisibilityBackup = movingJoint->Visibility.getValue();
|
||||
if (!jointVisibilityBackup) {
|
||||
movingJoint->Visibility.setValue(true);
|
||||
}
|
||||
|
||||
if (jointType == JointType::Revolute) {
|
||||
return DragMode::RotationOnPlane;
|
||||
}
|
||||
else if (jointType == JointType::Slider) {
|
||||
return DragMode::TranslationOnAxis;
|
||||
}
|
||||
else if (jointType == JointType::Cylindrical) {
|
||||
return DragMode::TranslationOnAxisAndRotationOnePlane;
|
||||
}
|
||||
else if (jointType == JointType::Ball) {
|
||||
// return DragMode::Ball;
|
||||
}
|
||||
else if (jointType == JointType::Distance) {
|
||||
// depends on the type of distance. For example plane-plane:
|
||||
// return DragMode::TranslationOnPlane;
|
||||
}
|
||||
}
|
||||
return DragMode::Translation;
|
||||
}
|
||||
|
||||
void ViewProviderAssembly::initMove()
|
||||
{
|
||||
Gui::Command::openCommand(tr("Move part").toStdString().c_str());
|
||||
partMoving = true;
|
||||
|
||||
// prevent selection while moving
|
||||
auto* view = dynamic_cast<Gui::View3DInventor*>(
|
||||
Gui::Application::Instance->editDocument()->getActiveView());
|
||||
if (view) {
|
||||
Gui::View3DInventorViewer* viewerNotConst;
|
||||
viewerNotConst = static_cast<Gui::View3DInventor*>(view)->getViewer();
|
||||
viewerNotConst->setSelectionEnabled(false);
|
||||
}
|
||||
|
||||
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
||||
"User parameter:BaseApp/Preferences/Mod/Assembly");
|
||||
bool solveOnMove = hGrp->GetBool("SolveOnMove", true);
|
||||
if (solveOnMove) {
|
||||
objectMasses.clear();
|
||||
for (auto& pair : docsToMove) {
|
||||
objectMasses.push_back({pair.first, 10.0});
|
||||
}
|
||||
|
||||
auto* assemblyPart = static_cast<AssemblyObject*>(getObject());
|
||||
assemblyPart->setObjMasses(objectMasses);
|
||||
/*std::vector<App::DocumentObject*> dragParts;
|
||||
for (auto& pair : docsToMove) {
|
||||
dragParts.push_back(pair.first);
|
||||
}
|
||||
assemblyPart->preDrag(dragParts);*/
|
||||
}
|
||||
}
|
||||
|
||||
void ViewProviderAssembly::endMove()
|
||||
{
|
||||
docsToMove = {};
|
||||
partMoving = false;
|
||||
canStartDragging = false;
|
||||
|
||||
if (movingJoint && !jointVisibilityBackup) {
|
||||
movingJoint->Visibility.setValue(false);
|
||||
}
|
||||
|
||||
movingJoint = nullptr;
|
||||
|
||||
// enable selection after the move
|
||||
auto* view = dynamic_cast<Gui::View3DInventor*>(
|
||||
Gui::Application::Instance->editDocument()->getActiveView());
|
||||
if (view) {
|
||||
Gui::View3DInventorViewer* viewerNotConst;
|
||||
viewerNotConst = static_cast<Gui::View3DInventor*>(view)->getViewer();
|
||||
viewerNotConst->setSelectionEnabled(true);
|
||||
}
|
||||
|
||||
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
||||
"User parameter:BaseApp/Preferences/Mod/Assembly");
|
||||
bool solveOnMove = hGrp->GetBool("SolveOnMove", true);
|
||||
if (solveOnMove) {
|
||||
auto* assemblyPart = static_cast<AssemblyObject*>(getObject());
|
||||
// assemblyPart->postDrag();
|
||||
assemblyPart->setObjMasses({});
|
||||
}
|
||||
|
||||
Gui::Command::commitCommand();
|
||||
}
|
||||
|
||||
void ViewProviderAssembly::onSelectionChanged(const Gui::SelectionChanges& msg)
|
||||
{
|
||||
if (msg.Type == Gui::SelectionChanges::AddSelection
|
||||
|| msg.Type == Gui::SelectionChanges::ClrSelection
|
||||
|| msg.Type == Gui::SelectionChanges::RmvSelection) {
|
||||
canStartDragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ViewProviderAssembly::onDelete(const std::vector<std::string>& subNames)
|
||||
{
|
||||
// Delete the joingroup when assembly is deleted
|
||||
for (auto obj : getObject()->getOutList()) {
|
||||
if (obj->getTypeId() == Assembly::JointGroup::getClassTypeId()) {
|
||||
obj->getDocument()->removeObject(obj->getNameInDocument());
|
||||
}
|
||||
}
|
||||
|
||||
return ViewProviderPart::onDelete(subNames);
|
||||
}
|
||||
|
||||
PyObject* ViewProviderAssembly::getPyObject()
|
||||
{
|
||||
if (!pyViewObject) {
|
||||
pyViewObject = new ViewProviderAssemblyPy(this);
|
||||
}
|
||||
pyViewObject->IncRef();
|
||||
return pyViewObject;
|
||||
}
|
||||
143
src/Mod/Assembly/Gui/ViewProviderAssembly.h
Normal file
@@ -0,0 +1,143 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef ASSEMBLYGUI_VIEWPROVIDER_ViewProviderAssembly_H
|
||||
#define ASSEMBLYGUI_VIEWPROVIDER_ViewProviderAssembly_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <Mod/Assembly/AssemblyGlobal.h>
|
||||
|
||||
#include <Gui/Selection.h>
|
||||
#include <Gui/ViewProviderPart.h>
|
||||
|
||||
namespace Gui
|
||||
{
|
||||
class View3DInventorViewer;
|
||||
}
|
||||
|
||||
namespace AssemblyGui
|
||||
{
|
||||
|
||||
class AssemblyGuiExport ViewProviderAssembly: public Gui::ViewProviderPart,
|
||||
public Gui::SelectionObserver
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(AssemblyGui::ViewProviderAssembly)
|
||||
PROPERTY_HEADER_WITH_OVERRIDE(AssemblyGui::ViewProviderAssembly);
|
||||
|
||||
enum class DragMode
|
||||
{
|
||||
Translation,
|
||||
TranslationOnAxis,
|
||||
TranslationOnPlane,
|
||||
Rotation,
|
||||
RotationOnPlane,
|
||||
TranslationOnAxisAndRotationOnePlane,
|
||||
Ball,
|
||||
None,
|
||||
};
|
||||
|
||||
public:
|
||||
ViewProviderAssembly();
|
||||
~ViewProviderAssembly() override;
|
||||
|
||||
/// deliver the icon shown in the tree view. Override from ViewProvider.h
|
||||
QIcon getIcon() const override;
|
||||
|
||||
bool doubleClicked() override;
|
||||
bool onDelete(const std::vector<std::string>& subNames) override;
|
||||
|
||||
/** @name enter/exit edit mode */
|
||||
//@{
|
||||
bool setEdit(int ModNum) override;
|
||||
void unsetEdit(int ModNum) override;
|
||||
bool isInEditMode() const;
|
||||
|
||||
/// Ask the view provider if it accepts object deletions while in edit
|
||||
bool acceptDeletionsInEdit() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canDragObject(App::DocumentObject*) const override;
|
||||
|
||||
App::DocumentObject* getActivePart() const;
|
||||
|
||||
|
||||
/// is called when the Provider is in edit and a key event ocours. Only ESC ends edit.
|
||||
bool keyPressed(bool pressed, int key) override;
|
||||
/// is called when the provider is in edit and the mouse is moved
|
||||
bool mouseMove(const SbVec2s& pos, Gui::View3DInventorViewer* viewer) override;
|
||||
/// is called when the Provider is in edit and the mouse is clicked
|
||||
bool mouseButtonPressed(int Button,
|
||||
bool pressed,
|
||||
const SbVec2s& cursorPos,
|
||||
const Gui::View3DInventorViewer* viewer) override;
|
||||
|
||||
/// Finds what drag mode should be used based on the user selection.
|
||||
DragMode findDragMode();
|
||||
void initMove();
|
||||
void endMove();
|
||||
virtual void setEnableMovement(bool enable = true)
|
||||
{
|
||||
enableMovement = enable;
|
||||
}
|
||||
virtual bool getEnableMovement() const
|
||||
{
|
||||
return enableMovement;
|
||||
}
|
||||
|
||||
bool getSelectedObjectsWithinAssembly();
|
||||
App::DocumentObject* getObjectFromSubNames(std::vector<std::string>& subNames);
|
||||
std::vector<std::string> parseSubNames(std::string& subNamesStr);
|
||||
|
||||
/// Get the python wrapper for that ViewProvider
|
||||
PyObject* getPyObject() override;
|
||||
|
||||
// protected:
|
||||
/// get called by the container whenever a property has been changed
|
||||
// void onChanged(const App::Property* prop) override;
|
||||
|
||||
void onSelectionChanged(const Gui::SelectionChanges& msg) override;
|
||||
|
||||
DragMode dragMode;
|
||||
bool canStartDragging;
|
||||
bool partMoving;
|
||||
bool enableMovement;
|
||||
bool jointVisibilityBackup;
|
||||
int numberOfSel;
|
||||
Base::Vector3d prevPosition;
|
||||
Base::Vector3d initialPosition;
|
||||
Base::Vector3d initialPositionRot;
|
||||
Base::Placement jcsPlc;
|
||||
Base::Placement jcsGlobalPlc;
|
||||
|
||||
App::DocumentObject* movingJoint;
|
||||
|
||||
std::vector<std::pair<App::DocumentObject*, double>> objectMasses;
|
||||
std::vector<std::pair<App::DocumentObject*, Base::Placement>> docsToMove;
|
||||
};
|
||||
|
||||
} // namespace AssemblyGui
|
||||
|
||||
#endif // ASSEMBLYGUI_VIEWPROVIDER_ViewProviderAssembly_H
|
||||
23
src/Mod/Assembly/Gui/ViewProviderAssemblyPy.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<GenerateModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="generateMetaModel_Module.xsd">
|
||||
<PythonExport
|
||||
Father="ViewProviderPy"
|
||||
Name="ViewProviderAssemblyPy"
|
||||
Twin="ViewProviderAssembly"
|
||||
TwinPointer="ViewProviderAssembly"
|
||||
Include="Mod/Assembly/Gui/ViewProviderAssembly.h"
|
||||
Namespace="AssemblyGui"
|
||||
FatherInclude="Gui/ViewProviderPy.h"
|
||||
FatherNamespace="Gui">
|
||||
<Documentation>
|
||||
<Author Licence="LGPL" Name="Ondsel" EMail="development@ondsel.com" />
|
||||
<UserDocu>This is the ViewProviderAssembly class</UserDocu>
|
||||
</Documentation>
|
||||
<Attribute Name="EnableMovement">
|
||||
<Documentation>
|
||||
<UserDocu>Enable moving the parts by clicking and dragging.</UserDocu>
|
||||
</Documentation>
|
||||
<Parameter Name="EnableMoving" Type="Boolean" />
|
||||
</Attribute>
|
||||
</PythonExport>
|
||||
</GenerateModel>
|
||||
59
src/Mod/Assembly/Gui/ViewProviderAssemblyPyImp.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2008 Werner Mayer <wmayer[at]users.sourceforge.net> *
|
||||
* *
|
||||
* 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"
|
||||
|
||||
// inclusion of the generated files (generated out of ViewProviderAssemblyPy.xml)
|
||||
#include "ViewProviderAssemblyPy.h"
|
||||
#include "ViewProviderAssemblyPy.cpp"
|
||||
|
||||
|
||||
using namespace Gui;
|
||||
|
||||
// returns a string which represents the object e.g. when printed in python
|
||||
std::string ViewProviderAssemblyPy::representation() const
|
||||
{
|
||||
std::stringstream str;
|
||||
str << "<Assembly View provider object at " << getViewProviderAssemblyPtr() << ">";
|
||||
|
||||
return str.str();
|
||||
}
|
||||
|
||||
Py::Boolean ViewProviderAssemblyPy::getEnableMovement() const
|
||||
{
|
||||
return {getViewProviderAssemblyPtr()->getEnableMovement()};
|
||||
}
|
||||
|
||||
void ViewProviderAssemblyPy::setEnableMovement(Py::Boolean arg)
|
||||
{
|
||||
getViewProviderAssemblyPtr()->setEnableMovement(arg);
|
||||
}
|
||||
|
||||
PyObject* ViewProviderAssemblyPy::getCustomAttributes(const char* /*attr*/) const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int ViewProviderAssemblyPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
55
src/Mod/Assembly/Gui/ViewProviderJointGroup.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#ifndef _PreComp_
|
||||
#endif
|
||||
|
||||
#include <App/Document.h>
|
||||
#include <App/DocumentObject.h>
|
||||
#include <Gui/Application.h>
|
||||
#include <Gui/BitmapFactory.h>
|
||||
|
||||
#include "ViewProviderJointGroup.h"
|
||||
|
||||
|
||||
using namespace AssemblyGui;
|
||||
|
||||
PROPERTY_SOURCE(AssemblyGui::ViewProviderJointGroup, Gui::ViewProviderDocumentObjectGroup)
|
||||
|
||||
ViewProviderJointGroup::ViewProviderJointGroup()
|
||||
{}
|
||||
|
||||
ViewProviderJointGroup::~ViewProviderJointGroup() = default;
|
||||
|
||||
QIcon ViewProviderJointGroup::getIcon() const
|
||||
{
|
||||
return Gui::BitmapFactory().pixmap("Assembly_CreateJointFixed.svg");
|
||||
}
|
||||
|
||||
// Make the joint group impossible to delete.
|
||||
bool ViewProviderJointGroup::onDelete(const std::vector<std::string>& subNames)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
69
src/Mod/Assembly/Gui/ViewProviderJointGroup.h
Normal file
@@ -0,0 +1,69 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef ASSEMBLYGUI_VIEWPROVIDER_ViewProviderJointGroup_H
|
||||
#define ASSEMBLYGUI_VIEWPROVIDER_ViewProviderJointGroup_H
|
||||
|
||||
#include <Mod/Assembly/AssemblyGlobal.h>
|
||||
|
||||
#include <Gui/ViewProviderDocumentObjectGroup.h>
|
||||
|
||||
|
||||
namespace AssemblyGui
|
||||
{
|
||||
|
||||
class AssemblyGuiExport ViewProviderJointGroup: public Gui::ViewProviderDocumentObjectGroup
|
||||
{
|
||||
PROPERTY_HEADER_WITH_OVERRIDE(AssemblyGui::ViewProviderJointGroup);
|
||||
|
||||
public:
|
||||
ViewProviderJointGroup();
|
||||
~ViewProviderJointGroup() override;
|
||||
|
||||
/// deliver the icon shown in the tree view. Override from ViewProvider.h
|
||||
QIcon getIcon() const override;
|
||||
|
||||
// Prevent dragging of the joints and dropping things inside the joint group.
|
||||
bool canDragObjects() const override
|
||||
{
|
||||
return false;
|
||||
};
|
||||
bool canDropObjects() const override
|
||||
{
|
||||
return false;
|
||||
};
|
||||
bool canDragAndDropObject(App::DocumentObject*) const override
|
||||
{
|
||||
return false;
|
||||
};
|
||||
|
||||
bool onDelete(const std::vector<std::string>& subNames) override;
|
||||
|
||||
// protected:
|
||||
/// get called by the container whenever a property has been changed
|
||||
// void onChanged(const App::Property* prop) override;
|
||||
};
|
||||
|
||||
} // namespace AssemblyGui
|
||||
|
||||
#endif // ASSEMBLYGUI_VIEWPROVIDER_ViewProviderJointGroup_H
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,7 +19,7 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
# Get the Parameter Group of this module
|
||||
ParGrp = App.ParamGet("System parameter:Modules").GetGroup("Assembly")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,7 +19,7 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
import Assembly_rc
|
||||
|
||||
@@ -49,7 +49,6 @@ class AssemblyWorkbench(Workbench):
|
||||
"Assembly workbench"
|
||||
|
||||
def __init__(self):
|
||||
print("Loading Assembly workbench...")
|
||||
self.__class__.Icon = (
|
||||
FreeCAD.getResourceDir() + "Mod/Assembly/Resources/icons/AssemblyWorkbench.svg"
|
||||
)
|
||||
@@ -57,7 +56,6 @@ class AssemblyWorkbench(Workbench):
|
||||
self.__class__.ToolTip = "Assembly workbench"
|
||||
|
||||
def Initialize(self):
|
||||
print("Initializing Assembly workbench...")
|
||||
global AssemblyCommandGroup
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
@@ -65,7 +63,7 @@ class AssemblyWorkbench(Workbench):
|
||||
# load the builtin modules
|
||||
from PySide import QtCore, QtGui
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
import CommandCreateAssembly, CommandInsertLink, CommandCreateJoint
|
||||
import CommandCreateAssembly, CommandInsertLink, CommandCreateJoint, CommandSolveAssembly, CommandExportASMT
|
||||
from Preferences import PreferencesPage
|
||||
|
||||
# from Preferences import preferences
|
||||
@@ -76,28 +74,35 @@ class AssemblyWorkbench(Workbench):
|
||||
FreeCADGui.addPreferencePage(PreferencesPage, QT_TRANSLATE_NOOP("QObject", "Assembly"))
|
||||
|
||||
# build commands list
|
||||
cmdlist = ["Assembly_CreateAssembly", "Assembly_InsertLink"]
|
||||
cmdList = [
|
||||
"Assembly_CreateAssembly",
|
||||
"Assembly_InsertLink",
|
||||
"Assembly_SolveAssembly",
|
||||
]
|
||||
|
||||
cmdListMenuOnly = [
|
||||
"Assembly_ExportASMT",
|
||||
]
|
||||
|
||||
cmdListJoints = [
|
||||
"Assembly_ToggleGrounded",
|
||||
"Separator",
|
||||
"Assembly_CreateJointFixed",
|
||||
"Assembly_CreateJointRevolute",
|
||||
"Assembly_CreateJointCylindrical",
|
||||
"Assembly_CreateJointSlider",
|
||||
"Assembly_CreateJointBall",
|
||||
"Assembly_CreateJointPlanar",
|
||||
"Assembly_CreateJointParallel",
|
||||
"Assembly_CreateJointTangent",
|
||||
"Assembly_CreateJointDistance",
|
||||
]
|
||||
|
||||
self.appendToolbar(QT_TRANSLATE_NOOP("Workbench", "Assembly"), cmdlist)
|
||||
self.appendToolbar(QT_TRANSLATE_NOOP("Workbench", "Assembly"), cmdList)
|
||||
self.appendToolbar(QT_TRANSLATE_NOOP("Workbench", "Assembly Joints"), cmdListJoints)
|
||||
|
||||
self.appendMenu(
|
||||
[QT_TRANSLATE_NOOP("Workbench", "&Assembly")],
|
||||
cmdlist + ["Separator"] + cmdListJoints,
|
||||
cmdList + cmdListMenuOnly + ["Separator"] + cmdListJoints,
|
||||
)
|
||||
|
||||
print("Assembly workbench loaded")
|
||||
|
||||
def Activated(self):
|
||||
# update the translation engine
|
||||
FreeCADGui.updateLocale()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,11 +19,13 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
def preferences():
|
||||
return FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Assembly")
|
||||
@@ -34,7 +36,15 @@ class PreferencesPage:
|
||||
self.form = FreeCADGui.PySideUic.loadUi(":preferences/Assembly.ui")
|
||||
|
||||
def saveSettings(self):
|
||||
pass
|
||||
pref = preferences()
|
||||
pref.SetBool("LeaveEditWithEscape", self.form.checkBoxEnableEscape.isChecked())
|
||||
pref.SetInt("GroundFirstPart", self.form.groundFirstPart.currentIndex())
|
||||
|
||||
def loadSettings(self):
|
||||
pass
|
||||
pref = preferences()
|
||||
self.form.checkBoxEnableEscape.setChecked(pref.GetBool("LeaveEditWithEscape", True))
|
||||
self.form.groundFirstPart.clear()
|
||||
self.form.groundFirstPart.addItem(translate("Assembly", "Ask"))
|
||||
self.form.groundFirstPart.addItem(translate("Assembly", "Always"))
|
||||
self.form.groundFirstPart.addItem(translate("Assembly", "Never"))
|
||||
self.form.groundFirstPart.setCurrentIndex(pref.GetInt("GroundFirstPart", 0))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,13 +19,12 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
import TestApp
|
||||
|
||||
from AssemblyTests.TestCore import TestCore
|
||||
|
||||
|
||||
# dummy usage to get flake8 and lgtm quiet
|
||||
False if TestCore.__name__ else True
|
||||
False if TestApp.__name__ else True
|
||||
# Use the modules so that code checkers don't complain (flake8)
|
||||
True if TestCore else False
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,13 +19,18 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
import FreeCAD as App
|
||||
import Part
|
||||
|
||||
if App.GuiUp:
|
||||
import FreeCADGui as Gui
|
||||
|
||||
import PySide.QtCore as QtCore
|
||||
import PySide.QtGui as QtGui
|
||||
|
||||
|
||||
# translate = App.Qt.translate
|
||||
|
||||
__title__ = "Assembly utilitary functions"
|
||||
@@ -36,17 +41,35 @@ __url__ = "https://www.freecad.org"
|
||||
def activeAssembly():
|
||||
doc = Gui.ActiveDocument
|
||||
|
||||
if doc is None or doc.ActiveView is None:
|
||||
return None
|
||||
|
||||
active_assembly = doc.ActiveView.getActiveObject("part")
|
||||
|
||||
if active_assembly is not None and active_assembly.Type == "Assembly":
|
||||
return active_assembly
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def activePart():
|
||||
doc = Gui.ActiveDocument
|
||||
|
||||
if doc is None or doc.ActiveView is None:
|
||||
return None
|
||||
|
||||
active_part = doc.ActiveView.getActiveObject("part")
|
||||
|
||||
if active_part is not None and active_part.Type == "Assembly":
|
||||
if active_part is not None and active_part.Type != "Assembly":
|
||||
return active_part
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def isAssemblyCommandActive():
|
||||
return activeAssembly() is not None and not Gui.Control.activeDialog()
|
||||
|
||||
|
||||
def isDocTemporary(doc):
|
||||
# Guard against older versions of FreeCad which don't have the Temporary attribute
|
||||
try:
|
||||
@@ -56,32 +79,299 @@ def isDocTemporary(doc):
|
||||
return temp
|
||||
|
||||
|
||||
def assembly_has_at_least_n_parts(n):
|
||||
assembly = activeAssembly()
|
||||
i = 0
|
||||
if not assembly:
|
||||
assembly = activePart()
|
||||
if not assembly:
|
||||
return False
|
||||
for obj in assembly.OutList:
|
||||
# note : groundedJoints comes in the outlist so we filter those out.
|
||||
if hasattr(obj, "Placement") and not hasattr(obj, "ObjectToGround"):
|
||||
i = i + 1
|
||||
if i == n:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def getObject(full_name):
|
||||
# full_name is "Assembly.Assembly1.Assembly2.Assembly3.Box.Edge16"
|
||||
# or "Assembly.Assembly1.Assembly2.Assembly3.Body.pad.Edge16"
|
||||
# We want either Body or Box.
|
||||
parts = full_name.split(".")
|
||||
# full_name is "Assembly.LinkOrAssembly1.LinkOrPart1.LinkOrBox.Edge16"
|
||||
# or "Assembly.LinkOrAssembly1.LinkOrPart1.LinkOrBody.pad.Edge16"
|
||||
# or "Assembly.LinkOrAssembly1.LinkOrPart1.LinkOrBody.Local_CS.X"
|
||||
# We want either LinkOrBody or LinkOrBox or Local_CS.
|
||||
names = full_name.split(".")
|
||||
doc = App.ActiveDocument
|
||||
if len(parts) < 3:
|
||||
|
||||
if len(names) < 3:
|
||||
App.Console.PrintError(
|
||||
"getObject() in UtilsAssembly.py the object name is too short, at minimum it should be something like 'Assembly.Box.edge16'. It shouldn't be shorter"
|
||||
)
|
||||
return None
|
||||
|
||||
obj = doc.getObject(parts[-3]) # So either 'Body', or 'Assembly'
|
||||
prevObj = None
|
||||
|
||||
if not obj:
|
||||
return None
|
||||
for i, objName in enumerate(names):
|
||||
if i == 0:
|
||||
prevObj = doc.getObject(objName)
|
||||
if prevObj.TypeId == "App::Link":
|
||||
prevObj = prevObj.getLinkedObject()
|
||||
continue
|
||||
|
||||
if obj.TypeId == "PartDesign::Body":
|
||||
return obj
|
||||
elif obj.TypeId == "App::Link":
|
||||
linked_obj = obj.getLinkedObject()
|
||||
if linked_obj.TypeId == "PartDesign::Body":
|
||||
obj = None
|
||||
if prevObj.TypeId in {"App::Part", "Assembly::AssemblyObject", "App::DocumentObjectGroup"}:
|
||||
for obji in prevObj.OutList:
|
||||
if obji.Name == objName:
|
||||
obj = obji
|
||||
break
|
||||
|
||||
if obj is None:
|
||||
return None
|
||||
|
||||
# the last is the element name. So if we are at the last but one name, then it must be the selected
|
||||
if i == len(names) - 2:
|
||||
return obj
|
||||
|
||||
else: # primitive, fastener, gear ... or link to primitive, fastener, gear...
|
||||
return doc.getObject(parts[-2])
|
||||
if obj.TypeId == "App::Link":
|
||||
linked_obj = obj.getLinkedObject()
|
||||
if linked_obj.TypeId == "PartDesign::Body":
|
||||
if i + 1 < len(names):
|
||||
obj2 = None
|
||||
for obji in linked_obj.OutList:
|
||||
if obji.Name == names[i + 1]:
|
||||
obj2 = obji
|
||||
break
|
||||
if obj2 and isBodySubObject(obj2.TypeId):
|
||||
return obj2
|
||||
return obj
|
||||
elif linked_obj.isDerivedFrom("Part::Feature"):
|
||||
return obj
|
||||
else:
|
||||
prevObj = linked_obj
|
||||
continue
|
||||
|
||||
elif obj.TypeId in {"App::Part", "Assembly::AssemblyObject", "App::DocumentObjectGroup"}:
|
||||
prevObj = obj
|
||||
continue
|
||||
|
||||
elif obj.TypeId == "PartDesign::Body":
|
||||
if i + 1 < len(names):
|
||||
obj2 = None
|
||||
for obji in obj.OutList:
|
||||
if obji.Name == names[i + 1]:
|
||||
obj2 = obji
|
||||
break
|
||||
if obj2 and isBodySubObject(obj2.TypeId):
|
||||
return obj2
|
||||
return obj
|
||||
|
||||
elif obj.isDerivedFrom("Part::Feature"):
|
||||
# primitive, fastener, gear ...
|
||||
return obj
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def isBodySubObject(typeId):
|
||||
return (
|
||||
typeId == "Sketcher::SketchObject"
|
||||
or typeId == "PartDesign::Point"
|
||||
or typeId == "PartDesign::Line"
|
||||
or typeId == "PartDesign::Plane"
|
||||
or typeId == "PartDesign::CoordinateSystem"
|
||||
)
|
||||
|
||||
|
||||
def getContainingPart(full_name, selected_object, activeAssemblyOrPart=None):
|
||||
# full_name is "Assembly.Assembly1.LinkOrPart1.LinkOrBox.Edge16" -> LinkOrPart1
|
||||
# or "Assembly.Assembly1.LinkOrPart1.LinkOrBody.pad.Edge16" -> LinkOrPart1
|
||||
# or "Assembly.Assembly1.LinkOrPart1.LinkOrBody.Sketch.Edge1" -> LinkOrBody
|
||||
|
||||
if selected_object is None:
|
||||
App.Console.PrintError("getContainingPart() in UtilsAssembly.py selected_object is None")
|
||||
return None
|
||||
|
||||
names = full_name.split(".")
|
||||
doc = App.ActiveDocument
|
||||
if len(names) < 3:
|
||||
App.Console.PrintError(
|
||||
"getContainingPart() in UtilsAssembly.py the object name is too short, at minimum it should be something like 'Assembly.Box.edge16'. It shouldn't be shorter"
|
||||
)
|
||||
return None
|
||||
|
||||
for objName in names:
|
||||
obj = doc.getObject(objName)
|
||||
|
||||
if not obj:
|
||||
continue
|
||||
|
||||
if obj == selected_object:
|
||||
return selected_object
|
||||
|
||||
if obj.TypeId == "PartDesign::Body" and isBodySubObject(selected_object.TypeId):
|
||||
if selected_object in obj.OutListRecursive:
|
||||
return obj
|
||||
|
||||
# Note here we may want to specify a specific behavior for Assembly::AssemblyObject.
|
||||
if obj.TypeId == "App::Part":
|
||||
if selected_object in obj.OutListRecursive:
|
||||
if not activeAssemblyOrPart:
|
||||
return obj
|
||||
elif activeAssemblyOrPart in obj.OutListRecursive or obj == activeAssemblyOrPart:
|
||||
continue
|
||||
else:
|
||||
return obj
|
||||
|
||||
elif obj.TypeId == "App::Link":
|
||||
linked_obj = obj.getLinkedObject()
|
||||
if linked_obj.TypeId == "PartDesign::Body" and isBodySubObject(selected_object.TypeId):
|
||||
if selected_object in linked_obj.OutListRecursive:
|
||||
return obj
|
||||
if linked_obj.TypeId == "App::Part":
|
||||
# linked_obj_doc = linked_obj.Document
|
||||
# selected_obj_in_doc = doc.getObject(selected_object.Name)
|
||||
if selected_object in linked_obj.OutListRecursive:
|
||||
if not activeAssemblyOrPart:
|
||||
return obj
|
||||
elif (linked_obj.Document == activeAssemblyOrPart.Document) and (
|
||||
activeAssemblyOrPart in linked_obj.OutListRecursive
|
||||
or linked_obj == activeAssemblyOrPart
|
||||
):
|
||||
continue
|
||||
else:
|
||||
return obj
|
||||
|
||||
# no container found so we return the object itself.
|
||||
return selected_object
|
||||
|
||||
|
||||
def getObjectInPart(objName, part):
|
||||
if part.Name == objName:
|
||||
return part
|
||||
|
||||
if part.TypeId == "App::Link":
|
||||
part = part.getLinkedObject()
|
||||
|
||||
if part.TypeId in {
|
||||
"App::Part",
|
||||
"Assembly::AssemblyObject",
|
||||
"App::DocumentObjectGroup",
|
||||
"PartDesign::Body",
|
||||
}:
|
||||
for obji in part.OutListRecursive:
|
||||
if obji.Name == objName:
|
||||
return obji
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# get the placement of Obj relative to its containing Part
|
||||
# Example : assembly.part1.part2.partn.body1 : placement of Obj relative to part1
|
||||
def getObjPlcRelativeToPart(objName, part):
|
||||
obj = getObjectInPart(objName, part)
|
||||
|
||||
# we need plc to be relative to the containing part
|
||||
obj_global_plc = getGlobalPlacement(obj, part)
|
||||
part_global_plc = getGlobalPlacement(part)
|
||||
|
||||
return part_global_plc.inverse() * obj_global_plc
|
||||
|
||||
|
||||
# Example : assembly.part1.part2.partn.body1 : jcsPlc is relative to body1
|
||||
# This function returns jcsPlc relative to part1
|
||||
def getJcsPlcRelativeToPart(jcsPlc, objName, part):
|
||||
obj_relative_plc = getObjPlcRelativeToPart(objName, part)
|
||||
return obj_relative_plc * jcsPlc
|
||||
|
||||
|
||||
# Return the jcs global placement
|
||||
def getJcsGlobalPlc(jcsPlc, objName, part):
|
||||
obj = getObjectInPart(objName, part)
|
||||
|
||||
obj_global_plc = getGlobalPlacement(obj, part)
|
||||
return obj_global_plc * jcsPlc
|
||||
|
||||
|
||||
# The container is used to support cases where the same object appears at several places
|
||||
# which happens when you have a link to a part.
|
||||
def getGlobalPlacement(targetObj, container=None):
|
||||
if targetObj is None:
|
||||
return App.Placement()
|
||||
|
||||
inContainerBranch = container is None
|
||||
for rootObj in App.activeDocument().RootObjects:
|
||||
foundPlacement = getTargetPlacementRelativeTo(
|
||||
targetObj, rootObj, container, inContainerBranch
|
||||
)
|
||||
if foundPlacement is not None:
|
||||
return foundPlacement
|
||||
|
||||
return App.Placement()
|
||||
|
||||
|
||||
def isThereOneRootAssembly():
|
||||
for part in App.activeDocument().RootObjects:
|
||||
if part.TypeId == "Assembly::AssemblyObject":
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def getTargetPlacementRelativeTo(
|
||||
targetObj, part, container, inContainerBranch, ignorePlacement=False
|
||||
):
|
||||
inContainerBranch = inContainerBranch or (not ignorePlacement and part == container)
|
||||
|
||||
if targetObj == part and inContainerBranch and not ignorePlacement:
|
||||
return targetObj.Placement
|
||||
|
||||
if part.TypeId == "App::DocumentObjectGroup":
|
||||
for obj in part.OutList:
|
||||
foundPlacement = getTargetPlacementRelativeTo(
|
||||
targetObj, obj, container, inContainerBranch, ignorePlacement
|
||||
)
|
||||
if foundPlacement is not None:
|
||||
return foundPlacement
|
||||
|
||||
elif part.TypeId in {"App::Part", "Assembly::AssemblyObject", "PartDesign::Body"}:
|
||||
for obj in part.OutList:
|
||||
foundPlacement = getTargetPlacementRelativeTo(
|
||||
targetObj, obj, container, inContainerBranch
|
||||
)
|
||||
if foundPlacement is None:
|
||||
continue
|
||||
|
||||
# If we were called from a link then we need to ignore this placement as we use the link placement instead.
|
||||
if not ignorePlacement:
|
||||
foundPlacement = part.Placement * foundPlacement
|
||||
|
||||
return foundPlacement
|
||||
|
||||
elif part.TypeId == "App::Link":
|
||||
linked_obj = part.getLinkedObject()
|
||||
if part == linked_obj or linked_obj is None:
|
||||
return None # upon loading this can happen for external links.
|
||||
|
||||
if linked_obj.TypeId in {"App::Part", "Assembly::AssemblyObject", "PartDesign::Body"}:
|
||||
for obj in linked_obj.OutList:
|
||||
foundPlacement = getTargetPlacementRelativeTo(
|
||||
targetObj, obj, container, inContainerBranch
|
||||
)
|
||||
if foundPlacement is None:
|
||||
continue
|
||||
|
||||
foundPlacement = part.Placement * foundPlacement
|
||||
return foundPlacement
|
||||
|
||||
foundPlacement = getTargetPlacementRelativeTo(
|
||||
targetObj, linked_obj, container, inContainerBranch, True
|
||||
)
|
||||
|
||||
if foundPlacement is not None and not ignorePlacement:
|
||||
foundPlacement = part.Placement * foundPlacement
|
||||
|
||||
return foundPlacement
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def getElementName(full_name):
|
||||
@@ -93,6 +383,10 @@ def getElementName(full_name):
|
||||
# At minimum "Assembly.Box.edge16". It shouldn't be shorter
|
||||
return ""
|
||||
|
||||
# case of PartDesign datums : CoordinateSystem, point, line, plane
|
||||
if parts[-1] in {"X", "Y", "Z", "Point", "Line", "Plane"}:
|
||||
return ""
|
||||
|
||||
return parts[-1]
|
||||
|
||||
|
||||
@@ -147,14 +441,25 @@ def extract_type_and_number(element_name):
|
||||
|
||||
|
||||
def findElementClosestVertex(selection_dict):
|
||||
obj = selection_dict["object"]
|
||||
|
||||
mousePos = selection_dict["mouse_pos"]
|
||||
|
||||
# We need mousePos to be relative to the part containing obj global placement
|
||||
if selection_dict["object"] != selection_dict["part"]:
|
||||
plc = App.Placement()
|
||||
plc.Base = mousePos
|
||||
global_plc = getGlobalPlacement(selection_dict["part"])
|
||||
plc = global_plc.inverse() * plc
|
||||
mousePos = plc.Base
|
||||
|
||||
elt_type, elt_index = extract_type_and_number(selection_dict["element_name"])
|
||||
|
||||
if elt_type == "Vertex":
|
||||
return selection_dict["element_name"]
|
||||
|
||||
elif elt_type == "Edge":
|
||||
edge = selection_dict["object"].Shape.Edges[elt_index - 1]
|
||||
|
||||
edge = obj.Shape.Edges[elt_index - 1]
|
||||
curve = edge.Curve
|
||||
if curve.TypeId == "Part::GeomCircle":
|
||||
# For centers, as they are not shape vertexes, we return the element name.
|
||||
@@ -162,17 +467,28 @@ def findElementClosestVertex(selection_dict):
|
||||
return selection_dict["element_name"]
|
||||
|
||||
edge_points = getPointsFromVertexes(edge.Vertexes)
|
||||
closest_vertex_index, _ = findClosestPointToMousePos(
|
||||
edge_points, selection_dict["mouse_pos"]
|
||||
)
|
||||
vertex_name = findVertexNameInObject(
|
||||
edge.Vertexes[closest_vertex_index], selection_dict["object"]
|
||||
)
|
||||
|
||||
if curve.TypeId == "Part::GeomLine":
|
||||
# For lines we allow users to select the middle of lines as well.
|
||||
line_middle = (edge_points[0] + edge_points[1]) * 0.5
|
||||
edge_points.append(line_middle)
|
||||
|
||||
closest_vertex_index, _ = findClosestPointToMousePos(edge_points, mousePos)
|
||||
|
||||
if curve.TypeId == "Part::GeomLine" and closest_vertex_index == 2:
|
||||
# If line center is closest then we have no vertex name to set so we put element name
|
||||
return selection_dict["element_name"]
|
||||
|
||||
vertex_name = findVertexNameInObject(edge.Vertexes[closest_vertex_index], obj)
|
||||
|
||||
return vertex_name
|
||||
|
||||
elif elt_type == "Face":
|
||||
face = selection_dict["object"].Shape.Faces[elt_index - 1]
|
||||
face = obj.Shape.Faces[elt_index - 1]
|
||||
surface = face.Surface
|
||||
_type = surface.TypeId
|
||||
if _type == "Part::GeomSphere" or _type == "Part::GeomTorus":
|
||||
return selection_dict["element_name"]
|
||||
|
||||
# Handle the circle/arc edges for their centers
|
||||
center_points = []
|
||||
@@ -181,19 +497,46 @@ def findElementClosestVertex(selection_dict):
|
||||
|
||||
for i, edge in enumerate(edges):
|
||||
curve = edge.Curve
|
||||
if curve.TypeId == "Part::GeomCircle":
|
||||
if curve.TypeId == "Part::GeomCircle" or curve.TypeId == "Part::GeomEllipse":
|
||||
center_points.append(curve.Location)
|
||||
center_points_edge_indexes.append(i)
|
||||
|
||||
elif _type == "Part::GeomCylinder" and curve.TypeId == "Part::GeomBSplineCurve":
|
||||
# handle special case of 2 cylinder intersecting.
|
||||
for j, facej in enumerate(obj.Shape.Faces):
|
||||
surfacej = facej.Surface
|
||||
if (elt_index - 1) != j and surfacej.TypeId == "Part::GeomCylinder":
|
||||
for edgej in facej.Edges:
|
||||
if edgej.Curve.TypeId == "Part::GeomBSplineCurve":
|
||||
if (
|
||||
edgej.CenterOfGravity == edge.CenterOfGravity
|
||||
and edgej.Length == edge.Length
|
||||
):
|
||||
center_points.append(edgej.CenterOfGravity)
|
||||
center_points_edge_indexes.append(i)
|
||||
|
||||
if len(center_points) > 0:
|
||||
closest_center_index, closest_center_distance = findClosestPointToMousePos(
|
||||
center_points, selection_dict["mouse_pos"]
|
||||
center_points, mousePos
|
||||
)
|
||||
|
||||
# Hendle the face vertexes
|
||||
face_points = getPointsFromVertexes(face.Vertexes)
|
||||
# Handle the face vertexes
|
||||
face_points = []
|
||||
|
||||
if _type != "Part::GeomCylinder" and _type != "Part::GeomCone":
|
||||
face_points = getPointsFromVertexes(face.Vertexes)
|
||||
|
||||
# We also allow users to select the center of gravity.
|
||||
if _type == "Part::GeomCylinder" or _type == "Part::GeomCone":
|
||||
centerOfG = face.CenterOfGravity - surface.Center
|
||||
centerPoint = surface.Center + centerOfG
|
||||
centerPoint = centerPoint + App.Vector().projectToLine(centerOfG, surface.Axis)
|
||||
face_points.append(centerPoint)
|
||||
else:
|
||||
face_points.append(face.CenterOfGravity)
|
||||
|
||||
closest_vertex_index, closest_vertex_distance = findClosestPointToMousePos(
|
||||
face_points, selection_dict["mouse_pos"]
|
||||
face_points, mousePos
|
||||
)
|
||||
|
||||
if len(center_points) > 0:
|
||||
@@ -202,9 +545,14 @@ def findElementClosestVertex(selection_dict):
|
||||
index = center_points_edge_indexes[closest_center_index] + 1
|
||||
return "Edge" + str(index)
|
||||
|
||||
vertex_name = findVertexNameInObject(
|
||||
face.Vertexes[closest_vertex_index], selection_dict["object"]
|
||||
)
|
||||
if _type == "Part::GeomCylinder" or _type == "Part::GeomCone":
|
||||
return selection_dict["element_name"]
|
||||
|
||||
if closest_vertex_index == len(face.Vertexes):
|
||||
# If center of gravity then we have no vertex name to set so we put element name
|
||||
return selection_dict["element_name"]
|
||||
|
||||
vertex_name = findVertexNameInObject(face.Vertexes[closest_vertex_index], obj)
|
||||
|
||||
return vertex_name
|
||||
|
||||
@@ -244,3 +592,54 @@ def color_from_unsigned(c):
|
||||
float(int((c >> 16) & 0xFF) / 255),
|
||||
float(int((c >> 8) & 0xFF) / 255),
|
||||
]
|
||||
|
||||
|
||||
def getJointGroup(assembly):
|
||||
joint_group = None
|
||||
|
||||
for obj in assembly.OutList:
|
||||
if obj.TypeId == "Assembly::JointGroup":
|
||||
joint_group = obj
|
||||
break
|
||||
|
||||
if not joint_group:
|
||||
joint_group = assembly.newObject("Assembly::JointGroup", "Joints")
|
||||
|
||||
return joint_group
|
||||
|
||||
|
||||
def isAssemblyGrounded():
|
||||
assembly = activeAssembly()
|
||||
if not assembly:
|
||||
return False
|
||||
|
||||
jointGroup = getJointGroup(assembly)
|
||||
|
||||
for joint in jointGroup.Group:
|
||||
if hasattr(joint, "ObjectToGround"):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def removeObjAndChilds(obj):
|
||||
removeObjsAndChilds([obj])
|
||||
|
||||
|
||||
def removeObjsAndChilds(objs):
|
||||
def addsubobjs(obj, toremoveset):
|
||||
if obj.TypeId == "App::Origin": # Origins are already handled
|
||||
return
|
||||
|
||||
toremoveset.add(obj)
|
||||
if obj.TypeId != "App::Link":
|
||||
for subobj in obj.OutList:
|
||||
addsubobjs(subobj, toremoveset)
|
||||
|
||||
toremove = set()
|
||||
for obj in objs:
|
||||
addsubobjs(obj, toremove)
|
||||
|
||||
for obj in toremove:
|
||||
if obj:
|
||||
obj.Document.removeObject(obj.Name)
|
||||
|
||||
@@ -111,6 +111,11 @@ locations = [
|
||||
],
|
||||
["App", "../App/Resources/translations", "../App/Resources/App.qrc"],
|
||||
["Arch", "../Mod/Arch/Resources/translations", "../Mod/Arch/Resources/Arch.qrc"],
|
||||
[
|
||||
"Assembly",
|
||||
"../Mod/Assembly/Gui/Resources/translations",
|
||||
"../Mod/Assembly/Gui/Resources/Assembly.qrc",
|
||||
],
|
||||
[
|
||||
"draft",
|
||||
"../Mod/Draft/Resources/translations",
|
||||
|
||||
@@ -67,6 +67,11 @@ directories = [
|
||||
"workingdir": "./src/Mod/Arch/",
|
||||
"tsdir": "Resources/translations",
|
||||
},
|
||||
{
|
||||
"tsname": "Assembly",
|
||||
"workingdir": "./src/Mod/Assembly/",
|
||||
"tsdir": "Gui/Resources/translations",
|
||||
},
|
||||
{
|
||||
"tsname": "Draft",
|
||||
"workingdir": "./src/Mod/Draft/",
|
||||
|
||||
@@ -78,6 +78,9 @@ set(TestExecutables
|
||||
Tests_run
|
||||
)
|
||||
|
||||
if(BUILD_ASSEMBLY)
|
||||
list (APPEND TestExecutables Assembly_tests_run)
|
||||
endif(BUILD_ASSEMBLY)
|
||||
if(BUILD_MATERIAL)
|
||||
list (APPEND TestExecutables Material_tests_run)
|
||||
endif(BUILD_MATERIAL)
|
||||
|
||||
@@ -316,4 +316,16 @@ TEST(Vector, TestIsParallelShortVectors)
|
||||
EXPECT_FALSE(vec.IsParallel(Base::Vector3d(0.01, 0.02, 0.04), 0.02));
|
||||
}
|
||||
|
||||
TEST(Vector, TestAngleOriented)
|
||||
{
|
||||
Base::Vector3d vec1(0.000001, 0, 0);
|
||||
Base::Vector3d vec2(0, 0.000001, 0);
|
||||
Base::Vector3d norm(0, 0, 0.000001);
|
||||
double angle = vec1.GetAngleOriented(vec2, norm);
|
||||
EXPECT_EQ(angle, Base::float_traits<double>::pi() * 0.5);
|
||||
angle = vec2.GetAngleOriented(vec1, norm);
|
||||
EXPECT_EQ(angle, Base::float_traits<double>::pi() * 1.5);
|
||||
}
|
||||
|
||||
// NOLINTEND
|
||||
|
||||
|
||||
57
tests/src/Mod/Assembly/App/AssemblyObject.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include <FCConfig.h>
|
||||
|
||||
#include <App/Application.h>
|
||||
#include <App/Document.h>
|
||||
#include <App/Expression.h>
|
||||
#include <App/ObjectIdentifier.h>
|
||||
#include <Mod/Assembly/App/AssemblyObject.h>
|
||||
#include <Mod/Assembly/App/JointGroup.h>
|
||||
#include <src/App/InitApplication.h>
|
||||
|
||||
class AssemblyObjectTest: public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
static void SetUpTestSuite()
|
||||
{
|
||||
tests::initApplication();
|
||||
}
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
_docName = App::GetApplication().getUniqueDocumentName("test");
|
||||
auto _doc = App::GetApplication().newDocument(_docName.c_str(), "testUser");
|
||||
_assemblyObj =
|
||||
static_cast<Assembly::AssemblyObject*>(_doc->addObject("Assembly::AssemblyObject"));
|
||||
_jointGroupObj = static_cast<Assembly::JointGroup*>(
|
||||
_assemblyObj->addObject("Assembly::JointGroup", "jointGroupTest"));
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
App::GetApplication().closeDocument(_docName.c_str());
|
||||
}
|
||||
|
||||
Assembly::AssemblyObject* getObject()
|
||||
{
|
||||
return _assemblyObj;
|
||||
}
|
||||
|
||||
private:
|
||||
// TODO: use shared_ptr or something else here?
|
||||
Assembly::AssemblyObject* _assemblyObj;
|
||||
Assembly::JointGroup* _jointGroupObj;
|
||||
std::string _docName;
|
||||
};
|
||||
|
||||
TEST_F(AssemblyObjectTest, createAssemblyObject) // NOLINT
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
}
|
||||
5
tests/src/Mod/Assembly/App/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
target_sources(
|
||||
Assembly_tests_run
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/AssemblyObject.cpp
|
||||
)
|
||||
15
tests/src/Mod/Assembly/CMakeLists.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
target_include_directories(Assembly_tests_run PUBLIC
|
||||
${EIGEN3_INCLUDE_DIR}
|
||||
${OCC_INCLUDE_DIR}
|
||||
${Python3_INCLUDE_DIRS}
|
||||
${XercesC_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(Assembly_tests_run
|
||||
gtest_main
|
||||
${Google_Tests_LIBS}
|
||||
Assembly
|
||||
)
|
||||
|
||||
add_subdirectory(App)
|
||||
@@ -1,3 +1,6 @@
|
||||
if(BUILD_ASSEMBLY)
|
||||
add_subdirectory(Assembly)
|
||||
endif(BUILD_ASSEMBLY)
|
||||
if(BUILD_MATERIAL)
|
||||
add_subdirectory(Material)
|
||||
endif(BUILD_MATERIAL)
|
||||
|
||||