Assembly: Adds a pre-solve when creating joint, preventing wrong orthogonal solutions from solver.
This commit is contained in:
@@ -162,6 +162,7 @@ App::DocumentObject* AssemblyObject::getJointOfPartConnectingToGround(App::Docum
|
||||
|
||||
bool AssemblyObject::isJointConnectingPartToGround(App::DocumentObject* joint, const char* propname)
|
||||
{
|
||||
|
||||
auto* propPart = dynamic_cast<App::PropertyLink*>(joint->getPropertyByName(propname));
|
||||
if (!propPart) {
|
||||
return false;
|
||||
@@ -173,14 +174,32 @@ bool AssemblyObject::isJointConnectingPartToGround(App::DocumentObject* joint, c
|
||||
return false;
|
||||
}
|
||||
|
||||
// now we disconnect this joint temporarily
|
||||
propPart->setValue(nullptr);
|
||||
// to know if a joint is connecting to ground we disable all the other joints
|
||||
std::vector<App::DocumentObject*> jointsOfPart = getJointsOfPart(part);
|
||||
std::vector<bool> activatedStates;
|
||||
|
||||
for (auto jointi : jointsOfPart) {
|
||||
if (jointi->getFullName() == joint->getFullName()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
activatedStates.push_back(getJointActivated(jointi));
|
||||
setJointActivated(jointi, false);
|
||||
}
|
||||
|
||||
isConnected = isPartConnected(part);
|
||||
|
||||
propPart->setValue(part);
|
||||
// restore activation states
|
||||
for (auto jointi : jointsOfPart) {
|
||||
if (jointi->getFullName() == joint->getFullName() || activatedStates.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return !isConnected;
|
||||
setJointActivated(jointi, activatedStates[0]);
|
||||
activatedStates.erase(activatedStates.begin());
|
||||
}
|
||||
|
||||
return isConnected;
|
||||
}
|
||||
|
||||
std::vector<App::DocumentObject*> AssemblyObject::getJoints(bool updateJCS)
|
||||
@@ -193,16 +212,20 @@ std::vector<App::DocumentObject*> AssemblyObject::getJoints(bool updateJCS)
|
||||
}
|
||||
|
||||
Base::PyGILStateLocker lock;
|
||||
for (auto obj : jointGroup->getObjects()) {
|
||||
if (!obj) {
|
||||
for (auto joint : jointGroup->getObjects()) {
|
||||
if (!joint) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto proxy = dynamic_cast<App::PropertyPythonObject*>(obj->getPropertyByName("Proxy"));
|
||||
auto* prop = dynamic_cast<App::PropertyBool*>(joint->getPropertyByName("Activated"));
|
||||
if (prop && !prop->getValue()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto proxy = dynamic_cast<App::PropertyPythonObject*>(joint->getPropertyByName("Proxy"));
|
||||
if (proxy) {
|
||||
Py::Object joint = proxy->getValue();
|
||||
if (joint.hasAttr("setJointConnectors")) {
|
||||
joints.push_back(obj);
|
||||
if (proxy->getValue().hasAttr("setJointConnectors")) {
|
||||
joints.push_back(joint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -347,16 +370,37 @@ bool AssemblyObject::isPartConnected(App::DocumentObject* obj)
|
||||
}
|
||||
|
||||
std::vector<App::DocumentObject*> AssemblyObject::getDownstreamParts(App::DocumentObject* part,
|
||||
int limit)
|
||||
App::DocumentObject* joint)
|
||||
{
|
||||
if (limit > 1000) { // Inifinite loop protection
|
||||
// First we deactivate the joint
|
||||
bool state = getJointActivated(joint);
|
||||
setJointActivated(joint, false);
|
||||
|
||||
std::vector<App::DocumentObject*> joints = getJoints(false);
|
||||
|
||||
std::set<App::DocumentObject*> connectedParts = {part};
|
||||
traverseAndMarkConnectedParts(part, connectedParts, joints);
|
||||
|
||||
std::vector<App::DocumentObject*> downstreamParts;
|
||||
for (auto parti : connectedParts) {
|
||||
if (!isPartConnected(parti) && (parti != part)) {
|
||||
downstreamParts.push_back(parti);
|
||||
}
|
||||
}
|
||||
|
||||
AssemblyObject::setJointActivated(joint, state);
|
||||
/*if (limit > 1000) { // Inifinite loop protection
|
||||
return {};
|
||||
}
|
||||
limit++;
|
||||
Base::Console().Warning("limit %d\n", limit);
|
||||
|
||||
std::vector<App::DocumentObject*> downstreamParts = {part};
|
||||
std::string name;
|
||||
App::DocumentObject* connectingJoint = getJointOfPartConnectingToGround(part, name);
|
||||
App::DocumentObject* connectingJoint =
|
||||
getJointOfPartConnectingToGround(part,
|
||||
name); // ?????????????????????????????? if we remove
|
||||
// connection to ground then it can't work for tom
|
||||
std::vector<App::DocumentObject*> jointsOfPart = getJointsOfPart(part);
|
||||
|
||||
// remove connectingJoint from jointsOfPart
|
||||
@@ -365,8 +409,23 @@ std::vector<App::DocumentObject*> AssemblyObject::getDownstreamParts(App::Docume
|
||||
for (auto joint : jointsOfPart) {
|
||||
App::DocumentObject* part1 = getLinkObjFromProp(joint, "Part1");
|
||||
App::DocumentObject* part2 = getLinkObjFromProp(joint, "Part2");
|
||||
App::DocumentObject* downstreamPart =
|
||||
part->getFullName() == part1->getFullName() ? part2 : part1;
|
||||
bool firstIsDown = part->getFullName() == part2->getFullName();
|
||||
App::DocumentObject* downstreamPart = firstIsDown ? part1 : part2;
|
||||
|
||||
Base::Console().Warning("looping\n");
|
||||
// it is possible that the part is connected to ground by this joint.
|
||||
// In which case we should not select those parts. To test we disconnect :
|
||||
auto* propObj = dynamic_cast<App::PropertyLink*>(joint->getPropertyByName("Part1"));
|
||||
if (!propObj) {
|
||||
continue;
|
||||
}
|
||||
propObj->setValue(nullptr);
|
||||
bool isConnected = isPartConnected(downstreamPart);
|
||||
propObj->setValue(part1);
|
||||
if (isConnected) {
|
||||
Base::Console().Warning("continue\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<App::DocumentObject*> subDownstreamParts =
|
||||
getDownstreamParts(downstreamPart, limit);
|
||||
@@ -376,7 +435,7 @@ std::vector<App::DocumentObject*> AssemblyObject::getDownstreamParts(App::Docume
|
||||
downstreamParts.push_back(downPart);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
return downstreamParts;
|
||||
}
|
||||
|
||||
@@ -1322,6 +1381,22 @@ void printPlacement(Base::Placement plc, const char* name)
|
||||
angle);
|
||||
}
|
||||
|
||||
void AssemblyObject::setJointActivated(App::DocumentObject* joint, bool val)
|
||||
{
|
||||
auto* propActivated = dynamic_cast<App::PropertyBool*>(joint->getPropertyByName("Activated"));
|
||||
if (propActivated) {
|
||||
propActivated->setValue(val);
|
||||
}
|
||||
}
|
||||
bool AssemblyObject::getJointActivated(App::DocumentObject* joint)
|
||||
{
|
||||
auto* propActivated = dynamic_cast<App::PropertyBool*>(joint->getPropertyByName("Activated"));
|
||||
if (propActivated) {
|
||||
return propActivated->getValue();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Base::Placement AssemblyObject::getPlacementFromProp(App::DocumentObject* obj, const char* propName)
|
||||
{
|
||||
Base::Placement plc = Base::Placement();
|
||||
@@ -1494,7 +1569,6 @@ App::DocumentObject* AssemblyObject::getLinkObjFromProp(App::DocumentObject* joi
|
||||
{
|
||||
auto* propObj = dynamic_cast<App::PropertyLink*>(joint->getPropertyByName(propLinkName));
|
||||
if (!propObj) {
|
||||
Base::Console().Warning("getLinkObjFromProp nullptr\n");
|
||||
return nullptr;
|
||||
}
|
||||
return propObj->getValue();
|
||||
|
||||
@@ -136,7 +136,8 @@ public:
|
||||
|
||||
bool isPartGrounded(App::DocumentObject* part);
|
||||
bool isPartConnected(App::DocumentObject* part);
|
||||
std::vector<App::DocumentObject*> getDownstreamParts(App::DocumentObject* part, int limit = 0);
|
||||
std::vector<App::DocumentObject*> getDownstreamParts(App::DocumentObject* part,
|
||||
App::DocumentObject* joint);
|
||||
std::vector<App::DocumentObject*> getUpstreamParts(App::DocumentObject* part, int limit = 0);
|
||||
App::DocumentObject* getUpstreamMovingPart(App::DocumentObject* part);
|
||||
|
||||
@@ -166,6 +167,8 @@ public:
|
||||
// see https://forum.freecad.org/viewtopic.php?p=729577#p729577
|
||||
|
||||
// getters to get from properties
|
||||
static void setJointActivated(App::DocumentObject* joint, bool val);
|
||||
static bool getJointActivated(App::DocumentObject* joint);
|
||||
static double getJointDistance(App::DocumentObject* joint);
|
||||
static JointType getJointType(App::DocumentObject* joint);
|
||||
static const char* getElementFromProp(App::DocumentObject* obj, const char* propName);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,7 +19,7 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
|
||||
def open(filename):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,7 +19,7 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
import FreeCAD as App
|
||||
|
||||
@@ -28,6 +28,9 @@ from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
if App.GuiUp:
|
||||
import FreeCADGui as Gui
|
||||
|
||||
import UtilsAssembly
|
||||
import Preferences
|
||||
|
||||
# translate = App.Qt.translate
|
||||
|
||||
__title__ = "Assembly Command Create Assembly"
|
||||
@@ -46,19 +49,32 @@ class CommandCreateAssembly:
|
||||
"Accel": "A",
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"Assembly_CreateAssembly",
|
||||
"Create an assembly object in the current document.",
|
||||
"Create an assembly object in the current document or if in the current active assembly if any. One root assembly per file max.",
|
||||
),
|
||||
"CmdType": "ForEdit",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
if Preferences.preferences().GetBool("EnforceOneAssemblyRule", True):
|
||||
activeAssembly = UtilsAssembly.activeAssembly()
|
||||
|
||||
if UtilsAssembly.isThereOneRootAssembly() and not activeAssembly:
|
||||
return False
|
||||
|
||||
return App.ActiveDocument is not None
|
||||
|
||||
def Activated(self):
|
||||
App.setActiveTransaction("Create assembly")
|
||||
assembly = App.ActiveDocument.addObject("Assembly::AssemblyObject", "Assembly")
|
||||
|
||||
activeAssembly = UtilsAssembly.activeAssembly()
|
||||
if activeAssembly:
|
||||
assembly = activeAssembly.newObject("Assembly::AssemblyObject", "Assembly")
|
||||
else:
|
||||
assembly = App.ActiveDocument.addObject("Assembly::AssemblyObject", "Assembly")
|
||||
|
||||
assembly.Type = "Assembly"
|
||||
Gui.ActiveDocument.setEdit(assembly)
|
||||
if not activeAssembly:
|
||||
Gui.ActiveDocument.setEdit(assembly)
|
||||
assembly.newObject("Assembly::JointGroup", "Joints")
|
||||
App.closeActiveTransaction()
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,7 +19,7 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
import os
|
||||
import FreeCAD as App
|
||||
@@ -254,16 +254,15 @@ class CommandToggleGrounded:
|
||||
# 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:
|
||||
|
||||
full_element_name = UtilsAssembly.getFullElementName(sel.ObjectName, sub)
|
||||
obj = UtilsAssembly.getObject(full_element_name)
|
||||
part_containing_obj = UtilsAssembly.getContainingPart(full_element_name, obj)
|
||||
|
||||
# Only objects within the assembly.
|
||||
objs_names, element_name = UtilsAssembly.getObjsNamesAndElement(sel.ObjectName, sub)
|
||||
if assembly.Name not in objs_names:
|
||||
continue
|
||||
|
||||
full_element_name = UtilsAssembly.getFullElementName(sel.ObjectName, sub)
|
||||
obj = UtilsAssembly.getObject(full_element_name)
|
||||
part_containing_obj = UtilsAssembly.getContainingPart(full_element_name, obj)
|
||||
|
||||
# Check if part is grounded and if so delete the joint.
|
||||
for joint in joint_group.Group:
|
||||
if (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,7 +19,7 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
import FreeCAD as App
|
||||
import UtilsAssembly
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,7 +19,7 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
import re
|
||||
import os
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,7 +19,7 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
import os
|
||||
import FreeCAD as App
|
||||
|
||||
@@ -117,7 +117,6 @@ bool ViewProviderAssembly::canDragObject(App::DocumentObject* obj) const
|
||||
{
|
||||
Base::Console().Warning("ViewProviderAssembly::canDragObject\n");
|
||||
if (!obj || obj->getTypeId() == Assembly::JointGroup::getClassTypeId()) {
|
||||
Base::Console().Warning("so should be false...\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -245,6 +244,10 @@ bool ViewProviderAssembly::mouseMove(const SbVec2s& cursorPos, Gui::View3DInvent
|
||||
if (enableMovement && getSelectedObjectsWithinAssembly()) {
|
||||
moveMode = findMoveMode();
|
||||
|
||||
if (moveMode == MoveMode::None) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SbVec3f vec;
|
||||
if (moveMode == MoveMode::RotationOnPlane
|
||||
|| moveMode == MoveMode::TranslationOnAxisAndRotationOnePlane) {
|
||||
@@ -346,8 +349,13 @@ bool ViewProviderAssembly::mouseMove(const SbVec2s& cursorPos, Gui::View3DInvent
|
||||
}
|
||||
}
|
||||
|
||||
auto* assemblyPart = static_cast<AssemblyObject*>(getObject());
|
||||
assemblyPart->solve();
|
||||
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
||||
"User parameter:BaseApp/Preferences/Mod/Assembly");
|
||||
bool solveOnMove = hGrp->GetBool("SolveOnMove", true);
|
||||
if (solveOnMove) {
|
||||
auto* assemblyPart = static_cast<AssemblyObject*>(getObject());
|
||||
assemblyPart->solve();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -542,8 +550,11 @@ ViewProviderAssembly::MoveMode ViewProviderAssembly::findMoveMode()
|
||||
// actually move A
|
||||
App::DocumentObject* upstreamPart =
|
||||
assemblyPart->getUpstreamMovingPart(docsToMove[0].first);
|
||||
|
||||
docsToMove.clear();
|
||||
if (!upstreamPart) {
|
||||
return MoveMode::None;
|
||||
}
|
||||
|
||||
auto* propPlacement =
|
||||
dynamic_cast<App::PropertyPlacement*>(upstreamPart->getPropertyByName("Placement"));
|
||||
if (propPlacement) {
|
||||
@@ -570,8 +581,7 @@ ViewProviderAssembly::MoveMode ViewProviderAssembly::findMoveMode()
|
||||
jcsGlobalPlc = global_plc * jcsPlc;
|
||||
|
||||
// Add downstream parts so that they move together
|
||||
auto downstreamParts = assemblyPart->getDownstreamParts(docsToMove[0].first);
|
||||
docsToMove.clear(); // current [0] is added by the recursive getDownstreamParts.
|
||||
auto downstreamParts = assemblyPart->getDownstreamParts(docsToMove[0].first, joint);
|
||||
for (auto part : downstreamParts) {
|
||||
auto* propPlacement =
|
||||
dynamic_cast<App::PropertyPlacement*>(part->getPropertyByName("Placement"));
|
||||
@@ -648,6 +658,18 @@ void ViewProviderAssembly::onSelectionChanged(const Gui::SelectionChanges& msg)
|
||||
}
|
||||
}
|
||||
|
||||
bool ViewProviderAssembly::onDelete(const std::vector<std::string>& subNames)
|
||||
{
|
||||
// Delete the joingroup when assembly is deleted
|
||||
for (auto obj : getObject()->getOutList()) {
|
||||
if (obj->getTypeId() == Assembly::JointGroup::getClassTypeId()) {
|
||||
obj->getDocument()->removeObject(obj->getNameInDocument());
|
||||
}
|
||||
}
|
||||
|
||||
return ViewProviderPart::onDelete(subNames);
|
||||
}
|
||||
|
||||
PyObject* ViewProviderAssembly::getPyObject()
|
||||
{
|
||||
if (!pyViewObject) {
|
||||
|
||||
@@ -53,6 +53,7 @@ public:
|
||||
QIcon getIcon() const override;
|
||||
|
||||
bool doubleClicked() override;
|
||||
bool onDelete(const std::vector<std::string>& subNames) override;
|
||||
|
||||
/** @name enter/exit edit mode */
|
||||
//@{
|
||||
@@ -77,6 +78,7 @@ public:
|
||||
Rotation,
|
||||
RotationOnPlane,
|
||||
TranslationOnAxisAndRotationOnePlane,
|
||||
None,
|
||||
};
|
||||
MoveMode moveMode;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
* Suite 330, Boston, MA 02111-1307, USA *
|
||||
* *
|
||||
***************************************************************************/
|
||||
**************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,7 +19,7 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
# Get the Parameter Group of this module
|
||||
ParGrp = App.ParamGet("System parameter:Modules").GetGroup("Assembly")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,7 +19,7 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
import Assembly_rc
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,7 +19,7 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
import math
|
||||
|
||||
@@ -79,12 +79,6 @@ def solveIfAllowed(assembly, storePrev=False):
|
||||
assembly.solve(storePrev)
|
||||
|
||||
|
||||
def flipPlacement(plc, localXAxis):
|
||||
flipRot = App.Rotation(localXAxis, 180)
|
||||
plc.Rotation = plc.Rotation.multiply(flipRot)
|
||||
return plc
|
||||
|
||||
|
||||
class Joint:
|
||||
def __init__(self, joint, type_index, assembly):
|
||||
self.Type = "Joint"
|
||||
@@ -230,13 +224,14 @@ class Joint:
|
||||
|
||||
joint.addProperty(
|
||||
"App::PropertyBool",
|
||||
"FirstPartConnected",
|
||||
"Activated",
|
||||
"Joint",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"This indicate if the first part was connected to ground at the time of joint creation.",
|
||||
"This indicate if the joint is active.",
|
||||
),
|
||||
)
|
||||
joint.Activated = True
|
||||
|
||||
self.setJointConnectors(joint, [])
|
||||
|
||||
@@ -275,7 +270,7 @@ class Joint:
|
||||
|
||||
if len(current_selection) >= 1:
|
||||
joint.Part1 = None
|
||||
joint.FirstPartConnected = assembly.isPartConnected(current_selection[0]["part"])
|
||||
self.part1Connected = assembly.isPartConnected(current_selection[0]["part"])
|
||||
|
||||
joint.Object1 = current_selection[0]["object"].Name
|
||||
joint.Part1 = current_selection[0]["part"]
|
||||
@@ -290,8 +285,12 @@ class Joint:
|
||||
joint.Element1 = ""
|
||||
joint.Vertex1 = ""
|
||||
joint.Placement1 = App.Placement()
|
||||
self.partMovedByPresolved = None
|
||||
|
||||
if len(current_selection) >= 2:
|
||||
joint.Part2 = None
|
||||
self.part2Connected = assembly.isPartConnected(current_selection[1]["part"])
|
||||
|
||||
joint.Object2 = current_selection[1]["object"].Name
|
||||
joint.Part2 = current_selection[1]["part"]
|
||||
joint.Element2 = current_selection[1]["element_name"]
|
||||
@@ -299,7 +298,13 @@ class Joint:
|
||||
joint.Placement2 = self.findPlacement(
|
||||
joint, joint.Object2, joint.Part2, joint.Element2, joint.Vertex2, True
|
||||
)
|
||||
self.preventOrthogonal(joint)
|
||||
self.preSolve(
|
||||
joint,
|
||||
current_selection[0]["object"],
|
||||
joint.Part1,
|
||||
current_selection[1]["object"],
|
||||
joint.Part2,
|
||||
)
|
||||
solveIfAllowed(assembly, True)
|
||||
|
||||
else:
|
||||
@@ -309,6 +314,7 @@ class Joint:
|
||||
joint.Vertex2 = ""
|
||||
joint.Placement2 = App.Placement()
|
||||
assembly.undoSolve()
|
||||
self.undoPreSolve()
|
||||
|
||||
def updateJCSPlacements(self, joint):
|
||||
if not joint.Detach1:
|
||||
@@ -455,34 +461,29 @@ class Joint:
|
||||
plc.Rotation = rot * zRotation
|
||||
return plc
|
||||
|
||||
def flipPart(self, joint):
|
||||
if joint.FirstPartConnected:
|
||||
plc = joint.Placement2 # relative to obj
|
||||
obj = UtilsAssembly.getObjectInPart(joint.Object2, joint.Part2)
|
||||
def flipPlacement(self, plc):
|
||||
return self.applyRotationToPlacementAlongAxis(plc, 180, App.Vector(1, 0, 0))
|
||||
|
||||
# we need plc to be relative to the containing part
|
||||
obj_global_plc = UtilsAssembly.getGlobalPlacement(obj, joint.Part2)
|
||||
part_global_plc = UtilsAssembly.getGlobalPlacement(joint.Part2)
|
||||
plc = obj_global_plc * plc
|
||||
plc = part_global_plc.inverse() * plc
|
||||
|
||||
jcsXAxis = plc.Rotation.multVec(App.Vector(1, 0, 0))
|
||||
|
||||
joint.Part2.Placement = flipPlacement(joint.Part2.Placement, jcsXAxis)
|
||||
def flipOnePart(self, joint):
|
||||
if hasattr(self, "part2Connected") and not self.part2Connected:
|
||||
jcsPlc = UtilsAssembly.getJcsPlcRelativeToPart(
|
||||
joint.Placement2, joint.Object2, joint.Part2
|
||||
)
|
||||
globalJcsPlc = UtilsAssembly.getJcsGlobalPlc(
|
||||
joint.Placement2, joint.Object2, joint.Part2
|
||||
)
|
||||
jcsPlc = self.flipPlacement(jcsPlc)
|
||||
joint.Part2.Placement = globalJcsPlc * jcsPlc.inverse()
|
||||
|
||||
else:
|
||||
plc = joint.Placement1 # relative to obj
|
||||
obj = UtilsAssembly.getObjectInPart(joint.Object1, joint.Part1)
|
||||
|
||||
# we need plc to be relative to the containing part
|
||||
obj_global_plc = UtilsAssembly.getGlobalPlacement(obj, joint.Part1)
|
||||
part_global_plc = UtilsAssembly.getGlobalPlacement(joint.Part1)
|
||||
plc = obj_global_plc * plc
|
||||
plc = part_global_plc.inverse() * plc
|
||||
|
||||
jcsXAxis = plc.Rotation.multVec(App.Vector(1, 0, 0))
|
||||
|
||||
joint.Part1.Placement = flipPlacement(joint.Part1.Placement, jcsXAxis)
|
||||
jcsPlc = UtilsAssembly.getJcsPlcRelativeToPart(
|
||||
joint.Placement1, joint.Object1, joint.Part1
|
||||
)
|
||||
globalJcsPlc = UtilsAssembly.getJcsGlobalPlc(
|
||||
joint.Placement1, joint.Object1, joint.Part1
|
||||
)
|
||||
jcsPlc = self.flipPlacement(jcsPlc)
|
||||
joint.Part1.Placement = globalJcsPlc * jcsPlc.inverse()
|
||||
|
||||
solveIfAllowed(self.getAssembly(joint))
|
||||
|
||||
@@ -508,18 +509,55 @@ class Joint:
|
||||
return App.Vector(res[0].X, res[0].Y, res[0].Z)
|
||||
return surface.Center
|
||||
|
||||
def preventOrthogonal(self, joint):
|
||||
zAxis1 = joint.Placement1.Rotation.multVec(App.Vector(0, 0, 1))
|
||||
zAxis2 = joint.Placement2.Rotation.multVec(App.Vector(0, 0, 1))
|
||||
if abs(zAxis1.dot(zAxis2)) < Part.Precision.confusion():
|
||||
if joint.FirstPartConnected:
|
||||
joint.Part2.Placement = self.applyRotationToPlacementAlongAxis(
|
||||
joint.Part2.Placement, 30.0, App.Vector(1, 2, 0)
|
||||
)
|
||||
else:
|
||||
joint.Part1.Placement = self.applyRotationToPlacementAlongAxis(
|
||||
joint.Part1.Placement, 30.0, App.Vector(1, 2, 0)
|
||||
)
|
||||
def preSolve(self, joint, obj1, part1, obj2, part2):
|
||||
# The goal of this is to put the part in the correct position to avoid wrong placement by the solve.
|
||||
|
||||
# we actually don't want to match perfectly the JCS, it is best to match them
|
||||
# in the current closest direction, ie either matched or flipped.
|
||||
sameDir = self.areJcsSameDir(joint)
|
||||
|
||||
if hasattr(self, "part2Connected") and not self.part2Connected:
|
||||
self.partMovedByPresolved = joint.Part2
|
||||
self.presolveBackupPlc = joint.Part2.Placement
|
||||
|
||||
globalJcsPlc1 = UtilsAssembly.getJcsGlobalPlc(
|
||||
joint.Placement1, joint.Object1, joint.Part1
|
||||
)
|
||||
jcsPlc2 = UtilsAssembly.getJcsPlcRelativeToPart(
|
||||
joint.Placement2, joint.Object2, joint.Part2
|
||||
)
|
||||
if not sameDir:
|
||||
jcsPlc2 = self.flipPlacement(jcsPlc2)
|
||||
joint.Part2.Placement = globalJcsPlc1 * jcsPlc2.inverse()
|
||||
|
||||
elif hasattr(self, "part1Connected") and not self.part1Connected:
|
||||
self.partMovedByPresolved = joint.Part1
|
||||
self.presolveBackupPlc = joint.Part1.Placement
|
||||
|
||||
globalJcsPlc2 = UtilsAssembly.getJcsGlobalPlc(
|
||||
joint.Placement2, joint.Object2, joint.Part2
|
||||
)
|
||||
jcsPlc1 = UtilsAssembly.getJcsPlcRelativeToPart(
|
||||
joint.Placement1, joint.Object1, joint.Part1
|
||||
)
|
||||
if not sameDir:
|
||||
jcsPlc1 = self.flipPlacement(jcsPlc1)
|
||||
joint.Part1.Placement = globalJcsPlc2 * jcsPlc1.inverse()
|
||||
|
||||
def undoPreSolve(
|
||||
self,
|
||||
):
|
||||
if self.partMovedByPresolved:
|
||||
self.partMovedByPresolved.Placement = self.presolveBackupPlc
|
||||
self.partMovedByPresolved = None
|
||||
|
||||
def areJcsSameDir(self, joint):
|
||||
globalJcsPlc1 = UtilsAssembly.getJcsGlobalPlc(joint.Placement1, joint.Object1, joint.Part1)
|
||||
globalJcsPlc2 = UtilsAssembly.getJcsGlobalPlc(joint.Placement2, joint.Object2, joint.Part2)
|
||||
|
||||
zAxis1 = globalJcsPlc1.Rotation.multVec(App.Vector(0, 0, 1))
|
||||
zAxis2 = globalJcsPlc2.Rotation.multVec(App.Vector(0, 0, 1))
|
||||
return zAxis1.dot(zAxis2) > 0
|
||||
|
||||
|
||||
class ViewProviderJoint:
|
||||
@@ -567,7 +605,7 @@ class ViewProviderJoint:
|
||||
self.switch_JCS_preview = self.JCS_sep(self.transform3)
|
||||
|
||||
self.pick = coin.SoPickStyle()
|
||||
self.pick.style.setValue(coin.SoPickStyle.UNPICKABLE)
|
||||
self.setPickableState(True)
|
||||
|
||||
self.display_mode = coin.SoType.fromName("SoFCSelection").createInstance()
|
||||
self.display_mode.addChild(self.pick)
|
||||
@@ -682,20 +720,11 @@ class ViewProviderJoint:
|
||||
if joint.Object2:
|
||||
plc = joint.Placement2
|
||||
self.switch_JCS2.whichChild = coin.SO_SWITCH_ALL
|
||||
# if self.areJCSReversed(joint):
|
||||
# plc = flipPlacement(plc, App.Vector(1, 0, 0))
|
||||
|
||||
self.set_JCS_placement(self.transform2, plc, joint.Object2, joint.Part2)
|
||||
else:
|
||||
self.switch_JCS2.whichChild = coin.SO_SWITCH_NONE
|
||||
|
||||
def areJCSReversed(self, joint):
|
||||
zAxis1 = joint.Placement1.Rotation.multVec(App.Vector(0, 0, 1))
|
||||
zAxis2 = joint.Placement2.Rotation.multVec(App.Vector(0, 0, 1))
|
||||
|
||||
sameDir = zAxis1.dot(zAxis2) > 0
|
||||
return not sameDir
|
||||
|
||||
def showPreviewJCS(self, visible, placement=None, objName="", part=None):
|
||||
if visible:
|
||||
self.switch_JCS_preview.whichChild = coin.SO_SWITCH_ALL
|
||||
@@ -763,7 +792,7 @@ class ViewProviderJoint:
|
||||
def doubleClicked(self, vobj):
|
||||
assembly = vobj.Object.InList[0]
|
||||
if UtilsAssembly.activeAssembly() != assembly:
|
||||
Gui.ActiveDocument.ActiveView.setActiveObject("part", assembly)
|
||||
Gui.ActiveDocument.setEdit(assembly)
|
||||
|
||||
panel = TaskAssemblyCreateJoint(0, vobj.Object)
|
||||
Gui.Control.showDialog(panel)
|
||||
@@ -925,16 +954,19 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
|
||||
self.form.rotationSpinbox.valueChanged.connect(self.onRotationChanged)
|
||||
self.form.PushButtonReverse.clicked.connect(self.onReverseClicked)
|
||||
|
||||
Gui.Selection.clearSelection()
|
||||
|
||||
if jointObj:
|
||||
Gui.Selection.clearSelection()
|
||||
self.creating = False
|
||||
self.joint = jointObj
|
||||
self.jointName = jointObj.Label
|
||||
App.setActiveTransaction("Edit " + self.jointName + " Joint")
|
||||
|
||||
self.updateTaskboxFromJoint()
|
||||
self.visibilityBackup = self.joint.Visibility
|
||||
self.joint.Visibility = True
|
||||
|
||||
else:
|
||||
self.creating = True
|
||||
self.jointName = self.form.jointType.currentText().replace(" ", "")
|
||||
App.setActiveTransaction("Create " + self.jointName + " Joint")
|
||||
|
||||
@@ -942,6 +974,8 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
|
||||
self.preselection_dict = None
|
||||
|
||||
self.createJointObject()
|
||||
self.visibilityBackup = False
|
||||
self.handleInitialSelection()
|
||||
|
||||
self.toggleDistanceVisibility()
|
||||
self.toggleOffsetVisibility()
|
||||
@@ -964,13 +998,10 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
|
||||
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()
|
||||
|
||||
solveIfAllowed(self.assembly)
|
||||
self.joint.Visibility = self.visibilityBackup
|
||||
|
||||
App.closeActiveTransaction()
|
||||
return True
|
||||
@@ -978,6 +1009,8 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
|
||||
def reject(self):
|
||||
self.deactivate()
|
||||
App.closeActiveTransaction(True)
|
||||
if not self.creating: # update visibility only if we are editing the joint
|
||||
self.joint.Visibility = self.visibilityBackup
|
||||
return True
|
||||
|
||||
def deactivate(self):
|
||||
@@ -996,6 +1029,64 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
|
||||
if Gui.Control.activeDialog():
|
||||
Gui.Control.closeDialog()
|
||||
|
||||
def handleInitialSelection(self):
|
||||
selection = Gui.Selection.getSelectionEx("*", 0)
|
||||
if not selection:
|
||||
return
|
||||
for sel in selection:
|
||||
# If you select 2 solids (bodies for example) within an assembly.
|
||||
# There'll be a single sel but 2 SubElementNames.
|
||||
|
||||
if not sel.SubElementNames:
|
||||
# no subnames, so its a root assembly itself that is selected.
|
||||
Gui.Selection.removeSelection(sel.Object)
|
||||
continue
|
||||
|
||||
for sub_name in sel.SubElementNames:
|
||||
# Only objects within the assembly.
|
||||
objs_names, element_name = UtilsAssembly.getObjsNamesAndElement(
|
||||
sel.ObjectName, sub_name
|
||||
)
|
||||
if len(self.current_selection) >= 2 or self.assembly.Name not in objs_names:
|
||||
Gui.Selection.removeSelection(sel.Object, sub_name)
|
||||
continue
|
||||
|
||||
obj_name = sel.ObjectName
|
||||
|
||||
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)
|
||||
part_containing_selected_object = UtilsAssembly.getContainingPart(
|
||||
full_element_name, selected_object
|
||||
)
|
||||
|
||||
if selected_object == self.assembly:
|
||||
# do not accept selection of assembly itself
|
||||
Gui.Selection.removeSelection(sel.Object, sub_name)
|
||||
continue
|
||||
|
||||
if (
|
||||
len(self.current_selection) == 1
|
||||
and selected_object == self.current_selection[0]["object"]
|
||||
):
|
||||
# do not select several feature of the same object.
|
||||
Gui.Selection.removeSelection(sel.Object, sub_name)
|
||||
continue
|
||||
|
||||
selection_dict = {
|
||||
"object": selected_object,
|
||||
"part": part_containing_selected_object,
|
||||
"element_name": element_name,
|
||||
"full_element_name": full_element_name,
|
||||
"full_obj_name": full_obj_name,
|
||||
"vertex_name": element_name,
|
||||
}
|
||||
|
||||
self.current_selection.append(selection_dict)
|
||||
|
||||
self.updateJoint()
|
||||
|
||||
def createJointObject(self):
|
||||
type_index = self.form.jointType.currentIndex()
|
||||
|
||||
@@ -1022,7 +1113,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
|
||||
self.joint.Rotation = self.form.rotationSpinbox.property("rawValue")
|
||||
|
||||
def onReverseClicked(self):
|
||||
self.joint.Proxy.flipPart(self.joint)
|
||||
self.joint.Proxy.flipOnePart(self.joint)
|
||||
|
||||
def toggleDistanceVisibility(self):
|
||||
if self.form.jointType.currentText() in JointUsingDistance:
|
||||
@@ -1130,7 +1221,10 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
|
||||
def moveMouse(self, info):
|
||||
if len(self.current_selection) >= 2 or (
|
||||
len(self.current_selection) == 1
|
||||
and self.current_selection[0]["part"] == self.preselection_dict["part"]
|
||||
and (
|
||||
not self.preselection_dict
|
||||
or self.current_selection[0]["part"] == self.preselection_dict["part"]
|
||||
)
|
||||
):
|
||||
self.joint.ViewObject.Proxy.showPreviewJCS(False)
|
||||
return
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,7 +19,7 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,7 +19,7 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
import TestApp
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# /****************************************************************************
|
||||
# /**************************************************************************
|
||||
# *
|
||||
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
# *
|
||||
@@ -19,7 +19,7 @@
|
||||
# License along with FreeCAD. If not, see *
|
||||
# <https://www.gnu.org/licenses/>. *
|
||||
# *
|
||||
# ***************************************************************************/
|
||||
# **************************************************************************/
|
||||
|
||||
import FreeCAD as App
|
||||
import Part
|
||||
@@ -237,6 +237,33 @@ def getObjectInPart(objName, part):
|
||||
return None
|
||||
|
||||
|
||||
# get the placement of Obj relative to its containing Part
|
||||
# Example : assembly.part1.part2.partn.body1 : placement of Obj relative to part1
|
||||
def getObjPlcRelativeToPart(objName, part):
|
||||
obj = getObjectInPart(objName, part)
|
||||
|
||||
# we need plc to be relative to the containing part
|
||||
obj_global_plc = getGlobalPlacement(obj, part)
|
||||
part_global_plc = getGlobalPlacement(part)
|
||||
|
||||
return part_global_plc.inverse() * obj_global_plc
|
||||
|
||||
|
||||
# Example : assembly.part1.part2.partn.body1 : jcsPlc is relative to body1
|
||||
# This function returns jcsPlc relative to part1
|
||||
def getJcsPlcRelativeToPart(jcsPlc, objName, part):
|
||||
obj_relative_plc = getObjPlcRelativeToPart(objName, part)
|
||||
return obj_relative_plc * jcsPlc
|
||||
|
||||
|
||||
# Return the jcs global placement
|
||||
def getJcsGlobalPlc(jcsPlc, objName, part):
|
||||
obj = getObjectInPart(objName, part)
|
||||
|
||||
obj_global_plc = getGlobalPlacement(obj, part)
|
||||
return obj_global_plc * jcsPlc
|
||||
|
||||
|
||||
# The container is used to support cases where the same object appears at several places
|
||||
# which happens when you have a link to a part.
|
||||
def getGlobalPlacement(targetObj, container=None):
|
||||
@@ -249,6 +276,13 @@ def getGlobalPlacement(targetObj, container=None):
|
||||
return App.Placement()
|
||||
|
||||
|
||||
def isThereOneRootAssembly():
|
||||
for part in App.activeDocument().RootObjects:
|
||||
if part.TypeId == "Assembly::AssemblyObject":
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def getTargetPlacementRelativeTo(
|
||||
targetObj, part, container, inContainerBranch, ignorePlacement=False
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user