Assembly: Introduce core functionality of assembly workbench.

This commit is contained in:
Paddle
2023-09-20 18:45:47 +02:00
committed by PaddleStroke
parent 13d4cb128a
commit d77cd7acf5
39 changed files with 4229 additions and 391 deletions

View File

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

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

View File

@@ -0,0 +1,516 @@
// 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 <cmath>
#include <vector>
#include <unordered_map>
#endif
#include <App/Application.h>
#include <App/Document.h>
#include <App/DocumentObjectGroup.h>
#include <App/FeaturePythonPyImp.h>
#include <App/PropertyPythonObject.h>
#include <Base/Console.h>
#include <Base/Placement.h>
#include <Base/Rotation.h>
#include <Base/Tools.h>
#include <Base/Interpreter.h>
#include <OndselSolver/CREATE.h>
#include <OndselSolver/ASMTSimulationParameters.h>
#include <OndselSolver/ASMTAssembly.h>
#include <OndselSolver/ASMTMarker.h>
#include <OndselSolver/ASMTPart.h>
#include <OndselSolver/ASMTJoint.h>
#include <OndselSolver/ASMTFixedJoint.h>
#include <OndselSolver/ASMTRevoluteJoint.h>
#include <OndselSolver/ASMTCylindricalJoint.h>
#include <OndselSolver/ASMTTranslationalJoint.h>
#include <OndselSolver/ASMTSphericalJoint.h>
#include <OndselSolver/ASMTPointInPlaneJoint.h>
#include <OndselSolver/ASMTTime.h>
#include <OndselSolver/ASMTConstantGravity.h>
#include "AssemblyObject.h"
#include "AssemblyObjectPy.h"
#include "JointGroup.h"
using namespace App;
using namespace Assembly;
using namespace MbD;
PROPERTY_SOURCE(Assembly::AssemblyObject, App::Part)
AssemblyObject::AssemblyObject()
: mbdAssembly(std::make_shared<ASMTAssembly>())
{}
AssemblyObject::~AssemblyObject() = default;
PyObject* AssemblyObject::getPyObject()
{
if (PythonObject.is(Py::_None())) {
// ref counter is set to 1
PythonObject = Py::Object(new AssemblyObjectPy(this), true);
}
return Py::new_reference_to(PythonObject);
}
std::vector<App::DocumentObject*> AssemblyObject::getJoints()
{
std::vector<App::DocumentObject*> joints = {};
App::Document* doc = getDocument();
std::vector<DocumentObject*> jointGroups =
doc->getObjectsOfType(Assembly::JointGroup::getClassTypeId());
Base::PyGILStateLocker lock;
if (jointGroups.size() > 0) {
for (auto* obj : static_cast<App::DocumentObjectGroup*>(jointGroups[0])->getObjects()) {
App::PropertyPythonObject* proxy = obj
? dynamic_cast<App::PropertyPythonObject*>(obj->getPropertyByName("Proxy"))
: nullptr;
if (proxy) {
Py::Object joint = proxy->getValue();
if (joint.hasAttr("setJointConnectors")) {
joints.push_back(obj);
}
}
}
}
// Make sure the joints are up to date.
recomputeJointPlacements(joints);
return joints;
}
bool AssemblyObject::fixGroundedParts()
{
App::Document* doc = getDocument();
App::DocumentObject* jointsGroup = doc->getObject("Joints");
bool onePartFixed = false;
Base::PyGILStateLocker lock;
if (jointsGroup && jointsGroup->isDerivedFrom(App::DocumentObjectGroup::getClassTypeId())) {
for (auto* obj : static_cast<App::DocumentObjectGroup*>(jointsGroup)->getObjects()) {
auto* propObj =
dynamic_cast<App::PropertyLink*>(obj->getPropertyByName("ObjectToGround"));
if (propObj) {
App::DocumentObject* objToGround = propObj->getValue();
Base::Placement plc = getPlacementFromProp(obj, "Placement");
std::string str = obj->getFullName();
fixGroundedPart(objToGround, plc, str);
onePartFixed = true;
}
}
}
return onePartFixed;
}
void AssemblyObject::fixGroundedPart(App::DocumentObject* obj,
Base::Placement& plc,
std::string& name)
{
std::string markerName1 = "marker-" + obj->getFullName();
auto mbdMarker1 = makeMbdMarker(markerName1, plc);
mbdAssembly->addMarker(mbdMarker1);
std::shared_ptr<ASMTPart> mbdPart = getMbDPart(obj);
std::string markerName2 = "FixingMarker";
auto mbdMarker2 = makeMbdMarker(markerName2, plc);
mbdPart->addMarker(mbdMarker2);
markerName1 = "/OndselAssembly/" + mbdMarker1->name;
markerName2 = "/OndselAssembly/" + mbdPart->name + "/" + mbdMarker2->name;
auto mbdJoint = CREATE<ASMTFixedJoint>::With();
mbdJoint->setName(name);
mbdJoint->setMarkerI(markerName1);
mbdJoint->setMarkerJ(markerName2);
mbdAssembly->addJoint(mbdJoint);
}
void AssemblyObject::jointParts(std::vector<App::DocumentObject*> joints)
{
for (auto* joint : joints) {
std::shared_ptr<ASMTJoint> mbdJoint = makeMbdJoint(joint);
mbdAssembly->addJoint(mbdJoint);
}
}
Base::Placement AssemblyObject::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;
}
int AssemblyObject::solve()
{
// Base::Console().Warning("solve\n");
mbdAssembly = makeMbdAssembly();
objectPartMap.clear();
if (!fixGroundedParts()) {
// If no part fixed we can't solve.
return -6;
}
std::vector<App::DocumentObject*> joints = getJoints();
jointParts(joints);
try {
mbdAssembly->solve();
}
catch (...) {
Base::Console().Error("Solve failed\n");
return -1;
}
setNewPlacements();
// The Placement1 and Placement2 of each joint needs to be updated as the parts moved.
// Note calling only recomputeJointPlacements makes a weird illegal storage access
// When solving while moving part. Happens in Py::Callable(attr).apply();
// it apparantly can't access the JointObject 'updateJCSPlacements' function.
getJoints();
return 0;
}
void AssemblyObject::exportAsASMT(std::string fileName)
{
Base::Console().Warning("hello 1\n");
mbdAssembly = makeMbdAssembly();
objectPartMap.clear();
Base::Console().Warning("hello 2\n");
fixGroundedParts();
std::vector<App::DocumentObject*> joints = getJoints();
Base::Console().Warning("hello 3\n");
jointParts(joints);
Base::Console().Warning("hello 4\n");
Base::Console().Warning("%s\n", fileName.c_str());
mbdAssembly->outputFile(fileName);
Base::Console().Warning("hello 5\n");
}
std::shared_ptr<ASMTJoint> AssemblyObject::makeMbdJointOfType(JointType jointType)
{
std::shared_ptr<ASMTJoint> mbdJoint;
if (jointType == JointType::Fixed) {
mbdJoint = CREATE<ASMTFixedJoint>::With();
}
else if (jointType == JointType::Revolute) {
mbdJoint = CREATE<ASMTRevoluteJoint>::With();
}
else if (jointType == JointType::Cylindrical) {
mbdJoint = CREATE<ASMTCylindricalJoint>::With();
}
else if (jointType == JointType::Slider) {
mbdJoint = CREATE<ASMTTranslationalJoint>::With();
}
else if (jointType == JointType::Ball) {
mbdJoint = CREATE<ASMTSphericalJoint>::With();
}
else if (jointType == JointType::Planar) {
mbdJoint = CREATE<ASMTPointInPlaneJoint>::With();
}
else if (jointType == JointType::Parallel) {
// TODO
mbdJoint = CREATE<ASMTFixedJoint>::With();
}
else if (jointType == JointType::Tangent) {
// TODO
mbdJoint = CREATE<ASMTFixedJoint>::With();
}
return mbdJoint;
}
std::shared_ptr<ASMTJoint> AssemblyObject::makeMbdJoint(App::DocumentObject* joint)
{
JointType jointType = JointType::Fixed;
auto* prop = joint
? dynamic_cast<App::PropertyEnumeration*>(joint->getPropertyByName("JointType"))
: nullptr;
if (prop) {
jointType = static_cast<JointType>(prop->getValue());
}
std::shared_ptr<ASMTJoint> mbdJoint = makeMbdJointOfType(jointType);
std::string fullMarkerName1 = handleOneSideOfJoint(joint, "Object1", "Placement1");
std::string fullMarkerName2 = handleOneSideOfJoint(joint, "Object2", "Placement2");
mbdJoint->setMarkerI(fullMarkerName1);
mbdJoint->setMarkerJ(fullMarkerName2);
return mbdJoint;
}
std::shared_ptr<ASMTPart> AssemblyObject::getMbDPart(App::DocumentObject* obj)
{
std::shared_ptr<ASMTPart> mbdPart;
Base::Placement plc = getPlacementFromProp(obj, "Placement");
auto it = objectPartMap.find(obj);
if (it != objectPartMap.end()) {
// obj has been associated with an ASMTPart before
mbdPart = it->second;
}
else {
// obj has not been associated with an ASMTPart before
std::string str = obj->getFullName();
mbdPart = makeMbdPart(str, plc);
mbdAssembly->addPart(mbdPart);
objectPartMap[obj] = mbdPart; // Store the association
}
return mbdPart;
}
std::string AssemblyObject::handleOneSideOfJoint(App::DocumentObject* joint,
const char* propLinkName,
const char* propPlcName)
{
auto* propObj = dynamic_cast<App::PropertyLink*>(joint->getPropertyByName(propLinkName));
if (!propObj) {
return nullptr;
}
App::DocumentObject* obj = propObj->getValue();
std::shared_ptr<ASMTPart> mbdPart = getMbDPart(obj);
Base::Placement objPlc = getPlacementFromProp(obj, "Placement");
Base::Placement plc = getPlacementFromProp(joint, propPlcName);
// Now we have plc which is the JCS placement, but its relative to the doc origin, not to the
// obj.
plc = objPlc.inverse() * plc;
std::string markerName = joint->getFullName();
auto mbdMarker = makeMbdMarker(markerName, plc);
mbdPart->addMarker(mbdMarker);
return "/OndselAssembly/" + mbdPart->name + "/" + markerName;
}
std::shared_ptr<ASMTMarker> AssemblyObject::makeMbdMarker(std::string& name, Base::Placement& plc)
{
auto mbdMarker = CREATE<ASMTMarker>::With();
mbdMarker->setName(name);
Base::Vector3d pos = plc.getPosition();
mbdMarker->setPosition3D(pos.x, pos.y, pos.z);
// TODO : replace with quaternion to simplify
Base::Rotation rot = plc.getRotation();
Base::Matrix4D mat;
rot.getValue(mat);
Base::Vector3d r0 = mat.getRow(0);
Base::Vector3d r1 = mat.getRow(1);
Base::Vector3d r2 = mat.getRow(2);
mbdMarker->setRotationMatrix(r0.x, r0.y, r0.z, r1.x, r1.y, r1.z, r2.x, r2.y, r2.z);
/*double q0, q1, q2, q3;
rot.getValue(q0, q1, q2, q3);
mbdMarker->setQuarternions(q0, q1, q2, q3);*/
return mbdMarker;
}
std::shared_ptr<ASMTPart>
AssemblyObject::makeMbdPart(std::string& name, Base::Placement plc, double mass)
{
auto mdbPart = CREATE<ASMTPart>::With();
mdbPart->setName(name);
auto massMarker = CREATE<ASMTPrincipalMassMarker>::With();
massMarker->setMass(mass);
massMarker->setDensity(1.0);
massMarker->setMomentOfInertias(1.0, 1.0, 1.0);
mdbPart->setPrincipalMassMarker(massMarker);
Base::Vector3d pos = plc.getPosition();
mdbPart->setPosition3D(pos.x, pos.y, pos.z);
// Base::Console().Warning("MbD Part placement : (%f, %f, %f)\n", pos.x, pos.y, pos.z);
// TODO : replace with quaternion to simplify
Base::Rotation rot = plc.getRotation();
Base::Matrix4D mat;
rot.getValue(mat);
Base::Vector3d r0 = mat.getRow(0);
Base::Vector3d r1 = mat.getRow(1);
Base::Vector3d r2 = mat.getRow(2);
mdbPart->setRotationMatrix(r0.x, r0.y, r0.z, r1.x, r1.y, r1.z, r2.x, r2.y, r2.z);
/*double q0, q1, q2, q3;
rot.getValue(q0, q1, q2, q3);
mdbPart->setQuarternions(q0, q1, q2, q3);*/
return mdbPart;
}
std::shared_ptr<ASMTAssembly> AssemblyObject::makeMbdAssembly()
{
auto assembly = CREATE<ASMTAssembly>::With();
assembly->setName("OndselAssembly");
return assembly;
}
void AssemblyObject::setNewPlacements()
{
for (auto& pair : objectPartMap) {
App::DocumentObject* obj = pair.first;
std::shared_ptr<ASMTPart> mbdPart = pair.second;
if (!obj || !mbdPart) {
continue;
}
// Check if the object has a "Placement" property
auto* propPlacement =
dynamic_cast<App::PropertyPlacement*>(obj->getPropertyByName("Placement"));
if (propPlacement) {
double x, y, z;
mbdPart->getPosition3D(x, y, z);
// Base::Console().Warning("in set placement : (%f, %f, %f)\n", x, y, z);
Base::Vector3d pos = Base::Vector3d(x, y, z);
// TODO : replace with quaternion to simplify
auto& r0 = mbdPart->rotationMatrix->at(0);
auto& r1 = mbdPart->rotationMatrix->at(1);
auto& r2 = mbdPart->rotationMatrix->at(2);
Base::Vector3d row0 = Base::Vector3d(r0->at(0), r0->at(1), r0->at(2));
Base::Vector3d row1 = Base::Vector3d(r1->at(0), r1->at(1), r1->at(2));
Base::Vector3d row2 = Base::Vector3d(r2->at(0), r2->at(1), r2->at(2));
Base::Matrix4D mat;
mat.setRow(0, row0);
mat.setRow(1, row1);
mat.setRow(2, row2);
Base::Rotation rot = Base::Rotation(mat);
/*double q0, q1, q2, q3;
mbdPart->getQuarternions(q0, q1, q2, q3);
Base::Rotation rot = Base::Rotation(q0, q1, q2, q3);*/
Base::Placement newPlacement = Base::Placement(pos, rot);
propPlacement->setValue(newPlacement);
}
}
}
void AssemblyObject::recomputeJointPlacements(std::vector<App::DocumentObject*> joints)
{
// The Placement1 and Placement2 of each joint needs to be updated as the parts moved.
for (auto* joint : joints) {
App::PropertyPythonObject* proxy = joint
? dynamic_cast<App::PropertyPythonObject*>(joint->getPropertyByName("Proxy"))
: nullptr;
if (!proxy) {
continue;
}
Py::Object jointPy = proxy->getValue();
if (!jointPy.hasAttr("updateJCSPlacements")) {
continue;
}
Py::Object attr = jointPy.getAttr("updateJCSPlacements");
if (attr.ptr() && attr.isCallable()) {
Py::Tuple args(1);
args.setItem(0, Py::asObject(joint->getPyObject()));
Py::Callable(attr).apply(args);
}
}
}
double AssemblyObject::getObjMass(App::DocumentObject* obj)
{
for (auto& pair : objMasses) {
if (pair.first == obj) {
return pair.second;
}
}
return 1.0;
}
void AssemblyObject::setObjMasses(std::vector<std::pair<App::DocumentObject*, double>> objectMasses)
{
objMasses = objectMasses;
}
/*void Part::handleChangedPropertyType(Base::XMLReader& reader, const char* TypeName, App::Property*
prop)
{
App::Part::handleChangedPropertyType(reader, TypeName, prop);
}*/
/* Apparantly not necessary as App::Part doesn't have this.
// Python Assembly feature ---------------------------------------------------------
namespace App
{
/// @cond DOXERR
PROPERTY_SOURCE_TEMPLATE(Assembly::AssemblyObjectPython, Assembly::AssemblyObject)
template<>
const char* Assembly::AssemblyObjectPython::getViewProviderName() const
{
return "AssemblyGui::ViewProviderAssembly";
}
template<>
PyObject* Assembly::AssemblyObjectPython::getPyObject()
{
if (PythonObject.is(Py::_None())) {
// ref counter is set to 1
PythonObject = Py::Object(new FeaturePythonPyT<AssemblyObjectPy>(this), true);
}
return Py::new_reference_to(PythonObject);
}
/// @endcond
// explicit template instantiation
template class AssemblyExport FeaturePythonT<Assembly::AssemblyObject>;
}// namespace App*/

View File

@@ -0,0 +1,119 @@
// 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 <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
{
// This enum has to be the same as the one in JointObject.py
enum class JointType
{
Fixed,
Revolute,
Cylindrical,
Slider,
Ball,
Planar,
Parallel,
Tangent
};
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";
}
int solve();
void exportAsASMT(std::string fileName);
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::shared_ptr<MbD::ASMTJoint> makeMbdJoint(App::DocumentObject* joint);
std::shared_ptr<MbD::ASMTJoint> makeMbdJointOfType(JointType jointType);
std::string handleOneSideOfJoint(App::DocumentObject* joint,
const char* propObjLinkName,
const char* propPlcName);
void fixGroundedPart(App::DocumentObject* obj, Base::Placement& plc, std::string& jointName);
bool fixGroundedParts();
void jointParts(std::vector<App::DocumentObject*> joints);
std::vector<App::DocumentObject*> getJoints();
Base::Placement getPlacementFromProp(App::DocumentObject* obj, const char* propName);
void setNewPlacements();
void recomputeJointPlacements(std::vector<App::DocumentObject*> joints);
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;
// void handleChangedPropertyType(Base::XMLReader &reader, const char *TypeName, App::Property
// *prop) override;
};
// using AssemblyObjectPython = App::FeaturePythonT<AssemblyObject>;
} // namespace Assembly
#endif // ASSEMBLY_AssemblyObject_H

View File

@@ -0,0 +1,50 @@
<?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()
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="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,75 @@
/***************************************************************************
* 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)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
int ret = this->getAssemblyObjectPtr()->solve();
return Py_BuildValue("i", ret);
}
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,59 @@
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
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,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/>. *
* *
***************************************************************************/
#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>
#endif // _PreComp_
#endif // ASSEMBLY_PRECOMPILED_H

View File

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

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

View File

@@ -56,10 +56,10 @@ class CommandCreateAssembly:
def Activated(self):
App.setActiveTransaction("Create assembly")
assembly = App.ActiveDocument.addObject("App::Part", "Assembly")
assembly = App.ActiveDocument.addObject("Assembly::AssemblyObject", "Assembly")
assembly.Type = "Assembly"
Gui.ActiveDocument.ActiveView.setActiveObject("part", assembly)
assembly.newObject("App::DocumentObjectGroup", "Joints")
assembly.newObject("Assembly::JointGroup", "Joints")
App.closeActiveTransaction()

View File

@@ -31,6 +31,7 @@ if App.GuiUp:
from PySide import QtCore, QtGui, QtWidgets
import JointObject
from JointObject import TaskAssemblyCreateJoint
import UtilsAssembly
import Assembly_rc
@@ -51,10 +52,12 @@ class CommandCreateJointFixed:
"Pixmap": "Assembly_CreateJointFixed",
"MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointFixed", "Create Fixed Joint"),
"Accel": "F",
"ToolTip": QT_TRANSLATE_NOOP(
"ToolTip": "<p>"
+ 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: Permanently locks two parts together, preventing any movement or rotation.",
)
+ "</p>",
"CmdType": "ForEdit",
}
@@ -62,13 +65,8 @@ class CommandCreateJointFixed:
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, 0)
Gui.Control.showDialog(self.panel)
panel = TaskAssemblyCreateJoint(0)
Gui.Control.showDialog(panel)
class CommandCreateJointRevolute:
@@ -81,10 +79,12 @@ 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",
}
@@ -92,13 +92,8 @@ class CommandCreateJointRevolute:
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, 1)
Gui.Control.showDialog(self.panel)
panel = TaskAssemblyCreateJoint(1)
Gui.Control.showDialog(panel)
class CommandCreateJointCylindrical:
@@ -113,10 +108,12 @@ 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",
}
@@ -124,13 +121,8 @@ class CommandCreateJointCylindrical:
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, 2)
Gui.Control.showDialog(self.panel)
panel = TaskAssemblyCreateJoint(2)
Gui.Control.showDialog(panel)
class CommandCreateJointSlider:
@@ -143,10 +135,12 @@ 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",
}
@@ -154,13 +148,8 @@ class CommandCreateJointSlider:
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, 3)
Gui.Control.showDialog(self.panel)
panel = TaskAssemblyCreateJoint(3)
Gui.Control.showDialog(panel)
class CommandCreateJointBall:
@@ -173,10 +162,12 @@ 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",
}
@@ -184,13 +175,8 @@ class CommandCreateJointBall:
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, 4)
Gui.Control.showDialog(self.panel)
panel = TaskAssemblyCreateJoint(4)
Gui.Control.showDialog(panel)
class CommandCreateJointPlanar:
@@ -203,10 +189,12 @@ class CommandCreateJointPlanar:
"Pixmap": "Assembly_CreateJointPlanar",
"MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointPlanar", "Create Planar Joint"),
"Accel": "P",
"ToolTip": QT_TRANSLATE_NOOP(
"ToolTip": "<p>"
+ 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>",
),
"Create a Planar Joint: Ensures two selected features are in the same plane, restricting movement to that plane.",
)
+ "</p>",
"CmdType": "ForEdit",
}
@@ -214,13 +202,8 @@ class CommandCreateJointPlanar:
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, 5)
Gui.Control.showDialog(self.panel)
panel = TaskAssemblyCreateJoint(5)
Gui.Control.showDialog(panel)
class CommandCreateJointParallel:
@@ -233,10 +216,12 @@ class CommandCreateJointParallel:
"Pixmap": "Assembly_CreateJointParallel",
"MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointParallel", "Create Parallel Joint"),
"Accel": "L",
"ToolTip": QT_TRANSLATE_NOOP(
"ToolTip": "<p>"
+ QT_TRANSLATE_NOOP(
"Assembly_CreateJointParallel",
"<p>Create a Parallel Joint: Aligns two features to be parallel, constraining relative movement to parallel translations.</p>",
),
"Create a Parallel Joint: Aligns two features to be parallel, constraining relative movement to parallel translations.",
)
+ "</p>",
"CmdType": "ForEdit",
}
@@ -244,13 +229,8 @@ class CommandCreateJointParallel:
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)
panel = TaskAssemblyCreateJoint(6)
Gui.Control.showDialog(panel)
class CommandCreateJointTangent:
@@ -263,10 +243,39 @@ class CommandCreateJointTangent:
"Pixmap": "Assembly_CreateJointTangent",
"MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointTangent", "Create Tangent Joint"),
"Accel": "T",
"ToolTip": QT_TRANSLATE_NOOP(
"ToolTip": "<p>"
+ 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>",
),
"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):
panel = TaskAssemblyCreateJoint(7)
Gui.Control.showDialog(panel)
class CommandToggleGrounded:
def __init__(self):
pass
def GetResources(self):
return {
"Pixmap": "Assembly_ToggleGrounded",
"MenuText": QT_TRANSLATE_NOOP("Assembly_ToggleGrounded", "Toggle grounded"),
"Accel": "F",
"ToolTip": "<p>"
+ QT_TRANSLATE_NOOP(
"Assembly_ToggleGrounded",
"Toggle the grounded state of a part. Grounding a part permanently locks its position in the assembly, preventing any movement or rotation. You need at least one grounded part per assembly.",
)
+ "</p>",
"CmdType": "ForEdit",
}
@@ -277,224 +286,40 @@ class CommandCreateJointTangent:
assembly = UtilsAssembly.activeAssembly()
if not assembly:
return
view = Gui.activeDocument().activeView()
self.panel = TaskAssemblyCreateJoint(assembly, view, 7)
Gui.Control.showDialog(self.panel)
joint_group = UtilsAssembly.getJointGroup(assembly)
selection = Gui.Selection.getSelectionEx("*", 0)
if not selection:
return
class MakeJointSelGate:
def __init__(self, taskbox, assembly):
self.taskbox = taskbox
self.assembly = assembly
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:
def allow(self, doc, obj, sub):
if not sub:
return False
full_element_name = UtilsAssembly.getFullElementName(sel.ObjectName, sub)
obj = UtilsAssembly.getObject(full_element_name)
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)
# Check if part is grounded and if so delete the joint.
for joint in joint_group.Group:
if hasattr(joint, "ObjectToGround") and joint.ObjectToGround == obj:
doc = App.ActiveDocument
doc.removeObject(joint.Name)
doc.recompute()
return
# Create groundedJoint.
ground = joint_group.newObject("App::FeaturePython", "GroundedJoint")
JointObject.GroundedJoint(ground, obj)
JointObject.ViewProviderGroundedJoint(ground.ViewObject)
Gui.Selection.clearSelection()
Gui.Selection.addSelectionGate(
MakeJointSelGate(self, self.assembly), Gui.Selection.ResolveMode.NoResolve
)
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)
App.setActiveTransaction("Create joint")
self.createJointObject()
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()
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())

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.activeAssembly() is not None
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

@@ -110,6 +110,7 @@ class TaskAssemblyInsertLink(QtCore.QObject):
def deactivated(self):
if self.partMoving:
self.endMove()
self.doc.removeObject(self.createdLink.Name)
def buildPartList(self):
self.allParts.clear()

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": "F",
"ToolTip": "<p>"
+ QT_TRANSLATE_NOOP(
"Assembly_SolveAssembly",
"Solve the currently active assembly.",
)
+ "</p>",
"CmdType": "ForEdit",
}
def IsActive(self):
return UtilsAssembly.activeAssembly() is not None
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,43 @@
// 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>
#endif //_PreComp_
#endif // POINTSGUI_PRECOMPILED_H

View File

@@ -2,6 +2,7 @@
<qresource>
<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>
@@ -10,6 +11,7 @@
<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>panels/TaskAssemblyCreateJoint.ui</file>
<file>panels/TaskAssemblyInsertLink.ui</file>
<file>preferences/Assembly.ui</file>

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

@@ -0,0 +1,398 @@
// 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 <vector>
#include <sstream>
#include <iostream>
#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/Command.h>
#include <Gui/MDIView.h>
#include <Gui/View3DInventor.h>
#include <Gui/View3DInventorViewer.h>
#include <Mod/Assembly/App/AssemblyObject.h>
#include <Mod/PartDesign/App/Body.h>
#include "ViewProviderAssembly.h"
#include "ViewProviderAssemblyPy.h"
using namespace Assembly;
using namespace AssemblyGui;
PROPERTY_SOURCE(AssemblyGui::ViewProviderAssembly, Gui::ViewProviderPart)
ViewProviderAssembly::ViewProviderAssembly()
: SelectionObserver(true)
, canStartDragging(false)
, partMoving(false)
, enableMovement(true)
, 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()");
}
else {
// Part is not 'Active' so we enter edit mode to make it so.
Gui::Application::Instance->activeDocument()->setEdit(this);
}
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 = {};
// Set the part as not 'Activated' ie not bold in the tree.
Gui::Command::doCommand(Gui::Command::Gui,
"Gui.ActiveDocument.ActiveView.setActiveObject('%s', None)",
PARTKEY);
}
bool ViewProviderAssembly::isInEditMode()
{
App::DocumentObject* activePart = getActivePart();
if (!activePart) {
return false;
}
return activePart == this->getObject();
}
App::DocumentObject* ViewProviderAssembly::getActivePart()
{
App::DocumentObject* activePart = nullptr;
auto activeDoc = Gui::Application::Instance->activeDocument();
if (!activeDoc) {
activeDoc = getDocument();
}
auto activeView = activeDoc->setActiveView(this);
if (!activeView) {
return nullptr;
}
activePart = activeView->getActiveObject<App::DocumentObject*>(PARTKEY);
return activePart;
}
bool ViewProviderAssembly::mouseMove(const SbVec2s& cursorPos, Gui::View3DInventorViewer* viewer)
{
// Base::Console().Warning("Mouse move\n");
// Initialize or end the dragging of parts
if (canStartDragging) {
canStartDragging = false;
if (enableMovement && getSelectedObjectsWithinAssembly()) {
SbVec3f vec = viewer->getPointOnFocalPlane(cursorPos);
Base::Vector3d mousePosition = Base::Vector3d(vec[0], vec[1], vec[2]);
initMove(mousePosition);
}
}
// Do the dragging of parts
if (partMoving) {
SbVec3f vec = viewer->getPointOnFocalPlane(cursorPos);
Base::Vector3d mousePosition = 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 = propPlacement->getValue();
// Base::Console().Warning("transl %f %f %f\n", pair.second.x, pair.second.y,
// pair.second.z);
Base::Vector3d pos = mousePosition + pair.second;
Base::Placement newPlacement = Base::Placement(pos, plc.getRotation());
propPlacement->setValue(newPlacement);
}
}
auto* assemblyPart = static_cast<AssemblyObject*>(getObject());
assemblyPart->solve();
}
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) {
Base::Placement plc = propPlacement->getValue();
Base::Vector3d pos = plc.getPosition();
docsToMove.emplace_back(obj, pos);
}
}
}
}
// 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) {
if (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) {
Base::Placement plc = propPlacement->getValue();
Base::Vector3d pos = plc.getPosition();
docsToMove.emplace_back(preselectedObj, pos);
}
}
}
}
}
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());
}
else {
objName = subNames[subNames.size() - 3];
App::DocumentObject* obj = appDoc->getObject(objName.c_str());
if (!obj) {
return nullptr;
}
if (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->getTypeId().isDerivedFrom(PartDesign::Body::getClassTypeId())) {
return obj;
}
}
// then its neither a body or a link to a body.
objName = subNames[subNames.size() - 2];
return appDoc->getObject(objName.c_str());
}
}
void ViewProviderAssembly::initMove(Base::Vector3d& mousePosition)
{
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);
}
objectMasses.clear();
for (auto& pair : docsToMove) {
pair.second = pair.second - mousePosition;
objectMasses.push_back({pair.first, 10.0});
}
auto* assemblyPart = static_cast<AssemblyObject*>(getObject());
assemblyPart->setObjMasses(objectMasses);
}
void ViewProviderAssembly::endMove()
{
docsToMove = {};
partMoving = false;
canStartDragging = false;
// 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);
}
auto* assemblyPart = static_cast<AssemblyObject*>(getObject());
assemblyPart->setObjMasses({});
}
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;
}
}
PyObject* ViewProviderAssembly::getPyObject()
{
if (!pyViewObject) {
pyViewObject = new ViewProviderAssemblyPy(this);
}
pyViewObject->IncRef();
return pyViewObject;
}

View File

@@ -0,0 +1,106 @@
// 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 <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
{
PROPERTY_HEADER_WITH_OVERRIDE(AssemblyGui::ViewProviderAssembly);
public:
ViewProviderAssembly();
~ViewProviderAssembly() override;
/// deliver the icon shown in the tree view. Override from ViewProvider.h
QIcon getIcon() const override;
bool doubleClicked() override;
/** @name enter/exit edit mode */
//@{
bool setEdit(int ModNum) override;
void unsetEdit(int ModNum) override;
bool isInEditMode();
App::DocumentObject* getActivePart();
/// 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;
void initMove(Base::Vector3d& mousePosition);
void endMove();
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;
virtual void setEnableMovement(bool enable = true)
{
enableMovement = enable;
}
virtual bool getEnableMovement() const
{
return enableMovement;
}
// 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;
bool canStartDragging;
bool partMoving;
bool enableMovement;
int numberOfSel;
std::vector<std::pair<App::DocumentObject*, double>> objectMasses;
std::vector<std::pair<App::DocumentObject*, Base::Vector3d>> 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,49 @@
// 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");
}

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/>. *
* *
***************************************************************************/
#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;
// 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

@@ -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"
)
@@ -65,7 +64,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,8 +75,16 @@ 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",
"Assembly_ExportASMT",
]
cmdListJoints = [
"Assembly_ToggleGrounded",
"Separator",
"Assembly_CreateJointFixed",
"Assembly_CreateJointRevolute",
"Assembly_CreateJointCylindrical",

View File

@@ -26,6 +26,7 @@ import math
import FreeCAD as App
import Part
from PySide import QtCore
from PySide.QtCore import QT_TRANSLATE_NOOP
if App.GuiUp:
@@ -54,8 +55,9 @@ JointTypes = [
class Joint:
def __init__(self, joint, type_index):
self.Type = "Joint"
joint.Proxy = self
self.joint = joint
joint.addProperty(
"App::PropertyEnumeration",
@@ -130,7 +132,18 @@ class Joint:
),
)
self.setJointConnectors([])
self.setJointConnectors(joint, [])
def __getstate__(self):
return self.Type
def __setstate__(self, state):
if state:
self.Type = state
def setJointType(self, joint, jointType):
joint.JointType = jointType
joint.Label = jointType.replace(" ", "")
def onChanged(self, fp, prop):
"""Do something when a property has changed"""
@@ -142,34 +155,34 @@ class Joint:
# App.Console.PrintMessage("Recompute Python Box feature\n")
pass
def setJointConnectors(self, current_selection):
def setJointConnectors(self, joint, current_selection):
# current selection is a vector of strings like "Assembly.Assembly1.Assembly2.Body.Pad.Edge16" including both what selection return as obj_name and obj_sub
if len(current_selection) >= 1:
self.joint.Object1 = current_selection[0]["object"]
self.joint.Element1 = current_selection[0]["element_name"]
self.joint.Vertex1 = current_selection[0]["vertex_name"]
self.joint.Placement1 = self.findPlacement(
self.joint.Object1, self.joint.Element1, self.joint.Vertex1
)
joint.Object1 = current_selection[0]["object"]
joint.Element1 = current_selection[0]["element_name"]
joint.Vertex1 = current_selection[0]["vertex_name"]
joint.Placement1 = self.findPlacement(joint.Object1, joint.Element1, joint.Vertex1)
else:
self.joint.Object1 = None
self.joint.Element1 = ""
self.joint.Vertex1 = ""
self.joint.Placement1 = UtilsAssembly.activeAssembly().Placement
joint.Object1 = None
joint.Element1 = ""
joint.Vertex1 = ""
joint.Placement1 = UtilsAssembly.activeAssembly().Placement
if len(current_selection) >= 2:
self.joint.Object2 = current_selection[1]["object"]
self.joint.Element2 = current_selection[1]["element_name"]
self.joint.Vertex2 = current_selection[1]["vertex_name"]
self.joint.Placement2 = self.findPlacement(
self.joint.Object2, self.joint.Element2, self.joint.Vertex2
)
joint.Object2 = current_selection[1]["object"]
joint.Element2 = current_selection[1]["element_name"]
joint.Vertex2 = current_selection[1]["vertex_name"]
joint.Placement2 = self.findPlacement(joint.Object2, joint.Element2, joint.Vertex2)
else:
self.joint.Object2 = None
self.joint.Element2 = ""
self.joint.Vertex2 = ""
self.joint.Placement2 = UtilsAssembly.activeAssembly().Placement
joint.Object2 = None
joint.Element2 = ""
joint.Vertex2 = ""
joint.Placement2 = UtilsAssembly.activeAssembly().Placement
def updateJCSPlacements(self, joint):
joint.Placement1 = self.findPlacement(joint.Object1, joint.Element1, joint.Vertex1)
joint.Placement2 = self.findPlacement(joint.Object2, joint.Element2, joint.Vertex2)
"""
So here we want to find a placement that corresponds to a local coordinate system that would be placed at the selected vertex.
@@ -182,7 +195,11 @@ class Joint:
"""
def findPlacement(self, obj, elt, vtx):
plc = App.Placement(obj.Placement)
plc = App.Placement()
if not obj or not elt or not vtx:
return App.Placement()
elt_type, elt_index = UtilsAssembly.extract_type_and_number(elt)
vtx_type, vtx_index = UtilsAssembly.extract_type_and_number(vtx)
@@ -234,12 +251,18 @@ class Joint:
if surface.TypeId == "Part::GeomPlane":
plc.Rotation = App.Rotation(surface.Rotation)
# Now plc is the placement in the doc. But we need the placement relative to the solid origin.
return plc
class ViewProviderJoint:
def __init__(self, obj, app_obj):
def __init__(self, vobj):
"""Set this object to the proxy object of the actual view provider"""
vobj.Proxy = self
def attach(self, vobj):
"""Setup the scene sub-graph of the view provider, this method is mandatory"""
self.axis_thickness = 3
view_params = App.ParamGet("User parameter:BaseApp/Preferences/View")
@@ -258,11 +281,8 @@ class ViewProviderJoint:
self.cameraSensor = coin.SoFieldSensor(self.camera_callback, camera)
self.cameraSensor.attach(camera.height)
self.app_obj = app_obj
obj.Proxy = self
self.app_obj = vobj.Object
def attach(self, obj):
"""Setup the scene sub-graph of the view provider, this method is mandatory"""
self.transform1 = coin.SoTransform()
self.transform2 = coin.SoTransform()
self.transform3 = coin.SoTransform()
@@ -275,21 +295,21 @@ class ViewProviderJoint:
self.draw_style.style = coin.SoDrawStyle.LINES
self.draw_style.lineWidth = self.axis_thickness
self.switch_JCS1 = self.JCS_sep(obj, self.transform1)
self.switch_JCS2 = self.JCS_sep(obj, self.transform2)
self.switch_JCS_preview = self.JCS_sep(obj, self.transform3)
self.switch_JCS1 = self.JCS_sep(self.transform1)
self.switch_JCS2 = self.JCS_sep(self.transform2)
self.switch_JCS_preview = self.JCS_sep(self.transform3)
self.display_mode = coin.SoGroup()
self.display_mode.addChild(self.switch_JCS1)
self.display_mode.addChild(self.switch_JCS2)
self.display_mode.addChild(self.switch_JCS_preview)
obj.addDisplayMode(self.display_mode, "Wireframe")
vobj.addDisplayMode(self.display_mode, "Wireframe")
def camera_callback(self, *args):
scaleF = self.get_JCS_size()
self.axisScale.scaleFactor.setValue(scaleF, scaleF, scaleF)
def JCS_sep(self, obj, soTransform):
def JCS_sep(self, soTransform):
pick = coin.SoPickStyle()
pick.style.setValue(coin.SoPickStyle.UNPICKABLE)
@@ -424,21 +444,21 @@ class ViewProviderJoint:
self.x_axis_so_color.rgb.setValue(c[0], c[1], c[2])
def getIcon(self):
if self.app_obj.getPropertyByName("JointType") == "Fixed":
if self.app_obj.JointType == "Fixed":
return ":/icons/Assembly_CreateJointFixed.svg"
elif self.app_obj.getPropertyByName("JointType") == "Revolute":
elif self.app_obj.JointType == "Revolute":
return ":/icons/Assembly_CreateJointRevolute.svg"
elif self.app_obj.getPropertyByName("JointType") == "Cylindrical":
elif self.app_obj.JointType == "Cylindrical":
return ":/icons/Assembly_CreateJointCylindrical.svg"
elif self.app_obj.getPropertyByName("JointType") == "Slider":
elif self.app_obj.JointType == "Slider":
return ":/icons/Assembly_CreateJointSlider.svg"
elif self.app_obj.getPropertyByName("JointType") == "Ball":
elif self.app_obj.JointType == "Ball":
return ":/icons/Assembly_CreateJointBall.svg"
elif self.app_obj.getPropertyByName("JointType") == "Planar":
elif self.app_obj.JointType == "Planar":
return ":/icons/Assembly_CreateJointPlanar.svg"
elif self.app_obj.getPropertyByName("JointType") == "Parallel":
elif self.app_obj.JointType == "Parallel":
return ":/icons/Assembly_CreateJointParallel.svg"
elif self.app_obj.getPropertyByName("JointType") == "Tangent":
elif self.app_obj.JointType == "Tangent":
return ":/icons/Assembly_CreateJointTangent.svg"
return ":/icons/Assembly_CreateJoint.svg"
@@ -453,3 +473,374 @@ class ViewProviderJoint:
"""When restoring the serialized object from document we have the chance to set some internals here.\
Since no data were serialized nothing needs to be done here."""
return None
def doubleClicked(self, vobj):
panel = TaskAssemblyCreateJoint(0, vobj.Object)
Gui.Control.showDialog(panel)
################ Grounded Joint object #################
class GroundedJoint:
def __init__(self, joint, obj_to_ground):
self.Type = "GoundedJoint"
joint.Proxy = self
self.joint = joint
joint.addProperty(
"App::PropertyLink",
"ObjectToGround",
"Ground",
QT_TRANSLATE_NOOP("App::Property", "The object to ground"),
)
joint.ObjectToGround = obj_to_ground
joint.addProperty(
"App::PropertyPlacement",
"Placement",
"Ground",
QT_TRANSLATE_NOOP(
"App::Property",
"This is where the part is grounded.",
),
)
joint.Placement = obj_to_ground.Placement
def __getstate__(self):
return self.Type
def __setstate__(self, state):
if state:
self.Type = state
def onChanged(self, fp, prop):
"""Do something when a property has changed"""
# App.Console.PrintMessage("Change property: " + str(prop) + "\n")
pass
def execute(self, fp):
"""Do something when doing a recomputation, this method is mandatory"""
# App.Console.PrintMessage("Recompute Python Box feature\n")
pass
class ViewProviderGroundedJoint:
def __init__(self, obj):
"""Set this object to the proxy object of the actual view provider"""
obj.Proxy = self
def attach(self, obj):
"""Setup the scene sub-graph of the view provider, this method is mandatory"""
pass
def updateData(self, fp, prop):
"""If a property of the handled feature has changed we have the chance to handle this here"""
# fp is the handled feature, prop is the name of the property that has changed
pass
def getDisplayModes(self, obj):
"""Return a list of display modes."""
modes = ["Wireframe"]
return modes
def getDefaultDisplayMode(self):
"""Return the name of the default display mode. It must be defined in getDisplayModes."""
return "Wireframe"
def onChanged(self, vp, prop):
"""Here we can do something when a single property got changed"""
# App.Console.PrintMessage("Change property: " + str(prop) + "\n")
pass
def getIcon(self):
return ":/icons/Assembly_ToggleGrounded.svg"
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)
full_element_name = full_obj_name + "." + element_name
selected_object = UtilsAssembly.getObject(full_element_name)
for selection_dict in self.taskbox.current_selection:
if selection_dict["object"] == selected_object:
# 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, jointTypeIndex, jointObj=None):
super().__init__()
self.assembly = UtilsAssembly.activeAssembly()
self.view = Gui.activeDocument().activeView()
self.doc = App.ActiveDocument
if not self.assembly or not self.view or not self.doc:
return
self.assembly.ViewObject.EnableMovement = False
self.form = Gui.PySideUic.loadUi(":/panels/TaskAssemblyCreateJoint.ui")
self.form.jointType.addItems(JointTypes)
self.form.jointType.setCurrentIndex(jointTypeIndex)
self.form.jointType.currentIndexChanged.connect(self.onJointTypeChanged)
Gui.Selection.clearSelection()
if jointObj:
self.joint = jointObj
self.jointName = jointObj.Label
App.setActiveTransaction("Edit " + self.jointName + " Joint")
self.updateTaskboxFromJoint()
else:
self.jointName = self.form.jointType.currentText().replace(" ", "")
App.setActiveTransaction("Create " + self.jointName + " Joint")
self.current_selection = []
self.preselection_dict = None
self.createJointObject()
Gui.Selection.addSelectionGate(
MakeJointSelGate(self, self.assembly), Gui.Selection.ResolveMode.NoResolve
)
Gui.Selection.addObserver(self, Gui.Selection.ResolveMode.NoResolve)
Gui.Selection.setSelectionStyle(Gui.Selection.SelectionStyle.GreedySelection)
self.callbackMove = self.view.addEventCallback("SoLocation2Event", self.moveMouse)
self.callbackKey = self.view.addEventCallback("SoKeyboardEvent", self.KeyboardEvent)
def accept(self):
if len(self.current_selection) != 2:
App.Console.PrintWarning("You need to select 2 elements from 2 separate parts.")
return False
# Hide JSC's when joint is created and enable selection highlighting
# self.joint.ViewObject.Visibility = False
# self.joint.ViewObject.OnTopWhenSelected = "Enabled"
self.deactivate()
self.assembly.solve()
App.closeActiveTransaction()
return True
def reject(self):
self.deactivate()
App.closeActiveTransaction(True)
return True
def deactivate(self):
self.assembly.ViewObject.EnableMovement = True
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 = UtilsAssembly.getJointGroup(self.assembly)
self.joint = joint_group.newObject("App::FeaturePython", self.jointName)
Joint(self.joint, type_index)
ViewProviderJoint(self.joint.ViewObject)
def onJointTypeChanged(self, index):
self.joint.Proxy.setJointType(self.joint, self.form.jointType.currentText())
def updateTaskboxFromJoint(self):
self.current_selection = []
self.preselection_dict = None
selection_dict1 = {
"object": self.joint.Object1,
"element_name": self.joint.Element1,
"vertex_name": self.joint.Vertex1,
}
selection_dict2 = {
"object": self.joint.Object2,
"element_name": self.joint.Element2,
"vertex_name": self.joint.Vertex2,
}
self.current_selection.append(selection_dict1)
self.current_selection.append(selection_dict2)
elName = self.getObjSubNameFromObj(self.joint.Object1, self.joint.Element1)
"""print(
f"Gui.Selection.addSelection('{self.doc.Name}', '{self.joint.Object1.Name}', '{elName}')"
)"""
Gui.Selection.addSelection(self.doc.Name, self.joint.Object1.Name, elName)
elName = self.getObjSubNameFromObj(self.joint.Object2, self.joint.Element2)
Gui.Selection.addSelection(self.doc.Name, self.joint.Object2.Name, elName)
self.updateJointList()
def getObjSubNameFromObj(self, obj, elName):
if obj.TypeId == "PartDesign::Body":
return obj.Tip.Name + "." + elName
elif obj.TypeId == "App::Link":
linked_obj = obj.getLinkedObject()
if linked_obj.TypeId == "PartDesign::Body":
return linked_obj.Tip.Name + "." + elName
else:
return elName
else:
return elName
def updateJoint(self):
# First we build the listwidget
self.updateJointList()
# Then we pass the new list to the join object
self.joint.Proxy.setJointConnectors(self.joint, self.current_selection)
def updateJointList(self):
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["object"].Label + "." + sel["element_name"]
simplified_names.append(sname)
self.form.featureList.addItems(simplified_names)
def moveMouse(self, info):
if len(self.current_selection) >= 2 or (
len(self.current_selection) == 1
and self.current_selection[0]["object"] == self.preselection_dict["object"]
):
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)
selected_object = UtilsAssembly.getObject(full_element_name)
element_name = UtilsAssembly.getElementName(full_element_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["object"] == selected_object:
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()

View File

@@ -244,3 +244,11 @@ def color_from_unsigned(c):
float(int((c >> 16) & 0xFF) / 255),
float(int((c >> 8) & 0xFF) / 255),
]
def getJointGroup(assembly):
joint_group = assembly.getObject("Joints")
if not joint_group:
joint_group = assembly.newObject("Assembly::JointGroup", "Joints")
return joint_group