Merge pull request #10764 from Ondsel-Development/asm_card9

[Assembly] Solve the assembly (Card 9).
This commit is contained in:
Chris Hennes
2024-02-13 10:13:08 -06:00
committed by GitHub
68 changed files with 8970 additions and 610 deletions

View File

@@ -0,0 +1,4 @@
macro(SetupOndselSolverCpp)
# -------------------------------- OndselSolver --------------------------------
find_package(OndselSolver REQUIRED)
endmacro(SetupOndselSolverCpp)

View File

@@ -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,

View File

@@ -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..

View File

@@ -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()) {

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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 = ""

View 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);
}

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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>

View 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;
}

View 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
*/

View 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

View File

@@ -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})

View 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);
}

View 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

View 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>

View 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;
}

View 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"

View 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

View File

@@ -0,0 +1 @@
import AssemblyApp

View File

@@ -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):

View File

@@ -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))

View File

@@ -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
)

View File

@@ -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()

View File

@@ -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())

View 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())

View File

@@ -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:

View 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())

View 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);
}

View 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

View File

@@ -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

View 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"

View 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

View File

@@ -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>

View File

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@@ -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

View 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

View 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

View 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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View 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;
}

View 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

View 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>

View 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;
}

View 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;
}

View 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

View File

@@ -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")

View File

@@ -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()

File diff suppressed because it is too large Load Diff

View File

@@ -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))

View File

@@ -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

View File

@@ -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)

View File

@@ -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",

View File

@@ -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/",

View File

@@ -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)

View File

@@ -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

View 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
}

View File

@@ -0,0 +1,5 @@
target_sources(
Assembly_tests_run
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/AssemblyObject.cpp
)

View 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)

View File

@@ -1,3 +1,6 @@
if(BUILD_ASSEMBLY)
add_subdirectory(Assembly)
endif(BUILD_ASSEMBLY)
if(BUILD_MATERIAL)
add_subdirectory(Material)
endif(BUILD_MATERIAL)