Anuncios
Attention for Customers without Multi-Factor Authentication or Single Sign-On - OTP Verification rolls out April 2025. Read all about it here.

Connectors missing after disconnect from external event

Anonymous

Connectors missing after disconnect from external event

Anonymous
No aplicable

Good evening,

 

I have run into an issue that I cannot seem to figure out, no matter where I look, how I word the search, or what I try...

 

So here goes my first question on the ADN forums. Don't yell too loud :cara_con_una_leve_sonrisa:

 

To start, I'm running Revit 2018.2, the plugin is written as an external event, where I need some post processing done at idle time and the idle is immediately unwired after.

 

I have a need to disconnect taps from duct/pipe, post a command to Revit, and then reconnect the taps after the post command is completed.

 

I have no issues with the disconnect or the post command.

 

The problem is, when I go to reconnect the taps the connector on the duct/pipe that it was connected to no longer exists, neither in the ConnectorManager.Connectors or UnusedConnectors.

 

Normally, I would just call Connector.ConnectTo() and everything is fine, but once I call Connector.DisconnectFrom() to disconnect the tap, the connector on the duct/pipe that it was attached to gets deleted (in this case, connector id 3).

 

Therefore, when I go to reconnect the tap, and try to ConnectorManager.Lookup(3) to get the correct connector to reattach to - it comes back as null.

 

Looking in snoop, in fact the ConnectorSet size is always 2 after disconnect, and UnusedConnectors never gets anything dumped into it, so that's out.

 

I tried calling PlaceAsTap() on the tap, but that calls for the host connector, too, and I run into the same issue. It accepts a distance, so I tried using id 0 and a distance to see if it created id 3 at that distance - same result, 2 connectors in connector manager.

 

ConnectorElement.CreateDuctConnector(), but it complained about me not being in a family document. Which leads me to believe that's how I would add a connector to the family permanently, which would likely solve the issue for one part with one tap, but not others. And nobody likes bloat.

 

I tried disconnecting, and then calling ConnectorManager.Connectors.Insert(), but the set is always read only.

 

The manual solution is to move the tap to drop the connection, do the processing needed, and then move it back - the tap snaps back in place and id 3 or whatever shows back up in ConnectorManager.

 

If anyone can point me in the right direction I would really appreciate it.

 

Thank you

 

 

 

 

 

0 Me gusta
Responder
Soluciones aceptadas (1)
1.639 Vistas
9 Respuestas
Respuestas (9)

jeremytammik
Autodesk
Autodesk

This is the right place to ask.

 

Welcome to the forum.

 

If you have a solution that works manually through the UI, the best bet is to reproduce the same programmatically via the API, if you can.

 

In this case, that sounds entirely feasible:

 

Move the connectors away programmatically, and Revit will hopefully disconnect them for you.

 

Do your thing.

 

Move them back programmatically, and Revit will hopefully reconnect them again.

 

Here is my most thorough exploration of Revit's automatic connection functionality,using various different approaches to implement a rolling offset for pipes:

 

http://thebuildingcoder.typepad.com/blog/2014/01/final-rolling-offset-using-pipecreate.html

 

Good luck.

 

Cheers,

 

Jeremy

 

 

 



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

0 Me gusta

Anonymous
No aplicable
Thanks!

Yeah I figured out this was where to go.

Moving them manually renoves the connector from connector manager as well,
and it only snaps back if I click on the connector on the tap and move it
close to the part.

I'll give it a shot this morning and let you know what happens.

Thanks again,

Matt
0 Me gusta

Anonymous
No aplicable
Thanks for the article, too.

Unfortunately, after reading it the code I saw for both GetConnectorClosestTo() and Connect both have the same connector id pitfall that prompted this thread.

Still, moving might work - and if moving alone doesn't work, impersonating the mouse and forcing a click/drag should do it (i only found out the manual fix yesterday, at first i was only asked if I could disconnect/reconnect - which normally is simple.

I wish Connector.DisconnectFrom() had an option to preserve the connector - that would fix it and use way less lines of code than what I imagine imitating a mouse click drag will.

But anyway, I'll let you know, thanks again,

Matt
0 Me gusta

jeremytammik
Autodesk
Autodesk

Dear Matt,

 

I see no 'connector id pitfall' in GetConnectorClosestTo.

 

Are you talking about the implementation listed in 

 

http://thebuildingcoder.typepad.com/blog/2014/01/connecting-the-rolling-offset-pipe-to-its-neighbour...

 

Why do you say you wish Connector.DisconnectFrom had an 'option to preserve the connector'?

 

Doesn't it disconnect a connector A from a connector B, and afterwards A and B are both available, just like before, simply disconnected?

 

Can't you just connect A and B back together again afterwards?

 

In short, I have no idea what your issues might be.

 

Talking about code is sometimes pretty useless...

 

A reproducible case is all that counts:

 

http://thebuildingcoder.typepad.com/blog/about-the-author.html#1b

 

Cheers,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

0 Me gusta

Anonymous
No aplicable
Thanks again, but no - the code you referenced, although fantastic, does
not help my particular situation.

When I said connector of pitfall, I should have said missing connector
pitfall.

You cannot measure the distance between a point and a non-existent
connector, and even if I save off the connector origin beforehandand i
still cannot connect it - GetConnecorClosestTo() will do it's job but will
return a different connector.

As I mentioned, DisconnectFrom() deletes the automatically inserted
connector on the host part. If the connector did not disappear, I would
just reconnect it using Connector.ConnectTo(), and would have had no reason
to ask this question... The last thing i would want to do is waste
someone's time, especially in a public space.

As for why I need this done, other than that the connector disappearing I
cannot give you specifics without permission, except that reconnection is
what I was tasked with achieving.

Sorry for the confusion, and thanks again.

Matt
0 Me gusta

Anonymous
No aplicable

@jeremytammik

 

Although I believe I understood what you meant, I do not appreciate the allusion to me talking about code being pretty useless - as I understand it, that is the entire purpose of this forum.

 

I apologize if it seemed like I was doing the same when I stated I did not wish to waste anybody's time, I was not, I was simply stating that asking on here was a last ditch effort, because it disappoints me that I cannot figure this out on my own.

 

I made a few assumptions, firstly - because of the short amount of time I've been developing in Revit - that there must be something I am simply not seeing, This is the first time I have been absolutely unable to find a workaround for a problem, and I was hoping that someone else might have faced the same problem already and could give me a clue, that's why I dug and dug here and elsewhere for days before creating an account.

 

I also was just asking to be pointed in any direction that might help me, which I have gotten so far, so once again I appreciate your help.

 

However, based on how I have explained my situation thus far, I assumed it was understood that I cannot provide a drawing with example parts - they do not belong to me, and I would really like to keep my contract.

 

I could provide a decent code snippet, but not a drawing, that is something I CAN do - but the way I explained it I thought there was enough there to understand exactly how to recreate the problem with whatever pipe or duct you had, given the host part you were using only had two connectors defined in the Family.

 

So, that's my fault - like I said, first time asking a question on here.

 

I'll get you a bit of code for this either tonight or in the morning.

 

Thanks again, 

 

Matt

 

PS: Another possible miscommunication may be I did not mention that every function I mentioned in my original description of my case came from the Revit API - I was not discussing my own code other than explaining how I tried to implement these functions.

 

0 Me gusta

Anonymous
No aplicable

Hello all,

 

I am attaching a bare bones example showing the problem.

 

I cannot provide drawing files or items, so you will need a pipe or duct with two connectors defined in the family, and any number of taps attached to the pieces of duct or pipe.

 

Highlight everything, or at least one duct/pipe with a tap connected to it and then run this command.

 

Please let me know,

 

Thanks,

 

Matt

 

 

DisconnectIssueExample.cs:

 

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;

using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;

namespace ADNIssueExamples.Connectors
{
    [Transaction(TransactionMode.Manual)]
    [Regeneration(RegenerationOption.Manual)]
    public class DisappearingConnectors : IExternalCommand
    {

        #region Properties

        private int _idleFireCount = 0;

        /// <summary>
        ///     HostPartElementId > HostConnectorID, TapElementId, TapConnectorId
        /// </summary>
        private Dictionary<ElementId, List<Tuple<int, ElementId, int>>> _disconnectedTaps;

        #endregion //Properties

        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            var uiApp = commandData.Application;
            var uiDoc = uiApp.ActiveUIDocument;
            var doc = uiDoc.Document;

            var colUserSelectedIds = uiDoc.Selection.GetElementIds();
            var lstElementsToCheck = colUserSelectedIds.ToList();

            foreach (var elemId in lstElementsToCheck)
            {
                var fabPart = doc.GetElement(elemId) as FabricationPart;

                if (fabPart == null)
                {
                    var fabAssembly = uiDoc.Document.GetElement(elemId) as AssemblyInstance;

                    if (fabAssembly == null)
                    {
                        colUserSelectedIds.Remove(elemId);
                        continue;
                    }

                    fabPart = doc.GetElement(fabAssembly.GetMemberIds().First()) as FabricationPart;

                    if (fabPart == null)
                    {
                        colUserSelectedIds.Remove(elemId);
                        continue;
                    }
                }
            }

            if (colUserSelectedIds.Count == 0)
            {
                TaskDialog.Show("Disappearing Connectors", "Please select at least one fabrication part.");

                return Result.Cancelled;
            }

			
            try
            {
                if (!DisconnectTaps(doc, colUserSelectedIds))
                {
                    TaskDialog.Show("Disappearing Connectors", "No taps disconnected, please check selection.");

                    return Result.Failed;
                }
				
                //set selection set for posted command, not needed for example

                //var exportCommandId = RevitCommandId.LookupCommandId(_exportCommandString);

                //uiApp.PostCommand(exportCommandId);

                uiApp.Idling += revit_Idling;

                _idleFireCount = 0;

                return Result.Succeeded;
            }
            catch (Exception ex)
            {
                TaskDialog.Show("Disappearing Connectors", ex.Message);

                return Result.Failed;
            }
        }

        private bool DisconnectTaps(Document doc, ICollection<ElementId> colExportingPartIds)
        {
            var tapsDisconnected = false;
             
// Dictionary items: // Key = HostPartElementId > Values = HostConnectorID, TapElementId, TapConnectorId
_disconnectedTaps = new Dictionary<ElementId, List<Tuple<int, ElementId, int>>>(); using (var trans = new Transaction(doc, "Send to FAB Disconnect Taps")) { trans.Start(); foreach (ElementId elemId in colExportingPartIds) { var fabPart = doc.GetElement(elemId) as FabricationPart; //(Matt) Skip for now if it is a tap, we want to be from the perspective of the duct/pipe if (fabPart.IsATap() || fabPart.IsAHanger()) continue; var conManager = fabPart.ConnectorManager; foreach (Connector con in conManager.Connectors) { if (!con.IsConnected) continue; var connectedConnector = con.AllRefs .OfType<Connector>() .First(x => x.IsConnectedTo(con)); var connectedPart = connectedConnector.Owner as FabricationPart; if (connectedPart == null) continue; if (!connectedPart.IsATap()) continue; var tap = connectedPart; var tapCon = connectedConnector; var data = new Tuple<int, ElementId, int>(con.Id, tap.Id, tapCon.Id); if (_disconnectedTaps.ContainsKey(fabPart.Id)) { _disconnectedTaps[fabPart.Id].Add(data); } else { _disconnectedTaps.Add(fabPart.Id, new List<Tuple<int, ElementId, int>>()); _disconnectedTaps[fabPart.Id].Add(data); } con.DisconnectFrom(tapCon); tapsDisconnected = true; } } trans.Commit(); } return tapsDisconnected; } public void revit_Idling(object sender, Autodesk.Revit.UI.Events.IdlingEventArgs e) { //(Matt) the posted command doesn't fire until after the first idle if (_idleFireCount == 0) { _idleFireCount += 1; return; } var uiApp = sender as UIApplication; var uiDoc = uiApp.ActiveUIDocument; var doc = uiDoc.Document; try { ReconnectTaps(doc); } catch (Exception ex) { TaskDialog.Show("Disappearing Connectors", ex.Message); } finally { uiApp.Idling -= revit_Idling; } } private void ReconnectTaps(Document doc) { using (var trans = new Transaction(doc, "Reconnect Taps")) { trans.Start(); foreach (var entry in _disconnectedTaps) { foreach (var data in entry.Value) { var hostPart = doc.GetElement(entry.Key) as FabricationPart; var hostConManager = hostPart.ConnectorManager; var hostConCnt = hostConManager.Connectors.Size; var hostUnusedCnt = hostConManager.UnusedConnectors != null ? hostConManager.UnusedConnectors.Size : 0; var hostCon = hostConManager.Lookup(data.Item1); if (hostCon == null) throw new Exception("Unable to reconnect tap, host connector missing:\n\n" + "Host part id = " + hostPart.Id.IntegerValue.ToString() + "\n" + "Host conn id = " + data.Item1 + "\n" + "Host conn cnt = " + hostConCnt + "\n" + "Unused cnt = " + hostUnusedCnt); var tap = doc.GetElement(data.Item2) as FabricationPart; var tapConManager = tap.ConnectorManager; var tapConCnt = tapConManager.Connectors.Size; var tapUnusedCnt = tapConManager.UnusedConnectors != null ? tapConManager.UnusedConnectors.Size : 0; var tapCon = tapConManager.Lookup(data.Item3); if (tapCon == null) if (hostCon == null) throw new Exception("Unable to reconnect tap, tap connector missing:\n\n" + "Host part id = " + hostPart.Id.IntegerValue.ToString() + "\n" + "Host conn id = " + data.Item1 + "\n" + "Host conn cnt = " + hostConCnt + "\n" + "Host Unused cnt = " + hostUnusedCnt + "\n\n" + "Tap part id = " + tap.Id.IntegerValue.ToString() + "\n" + "Tap conn id = " + data.Item3 + "\n" + "Tap conn cnt = " + tapConCnt + "\n" + "Tap Unused cnt = " + tapUnusedCnt); hostCon.ConnectTo(tapCon); } } trans.Commit(); } } } }

 

 

0 Me gusta

Anonymous
No aplicable
Update:

Okay, I spoke with my client again,and asked some more questions, and found out something the might be the source of the problem and the confusion.

As it turns out, surprise to me, there is no actual family definition for a straight piece of duct or pipe. Who knew?

So, knowing that, I'm going back to things I've already tried and am going to give FabricationPart.PlaceAsTap() again.

If it works, awesome, I knew it as going to be a missing piece of information.

Hoped, anyway.

I'll post the results when I'm done if it does, I thought every item in Revit had a family definition.

0 Me gusta

Anonymous
No aplicable
Solución aceptada

Hello again,

 

Well I do not understand exactly why this did not work the first time I tried to use PlaceAsTap() - and granted it still does not give the exact results I want as written, but it is definitely a step in the right direction (see updated code below).

 

So thank goodness for that.

 

Thanks again,

 

Matt

 

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;

using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;

namespace ADNIssueExamples.Connectors
{
    [Transaction(TransactionMode.Manual)]
    [Regeneration(RegenerationOption.Manual)]
    public class DisappearingConnectors : IExternalCommand
    {

        #region Properties

        private int _idleFireCount = 0;

        ///// <summary>
        /////     Dictionary for holding tap connection information
        /////     HostPartElementId > HostConnectorID, TapElementId, TapConnectorId
        ///// </summary>
        //private Dictionary<ElementId, List<Tuple<int, ElementId, int>>> _disconnectedTaps;

        private Dictionary<ElementId, List<Tuple<int, double, ElementId, int, double, double>>> _disconnectedTaps;

        #endregion //Properties

        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            var uiApp = commandData.Application;
            var uiDoc = uiApp.ActiveUIDocument;
            var doc = uiDoc.Document;

            var colUserSelectedIds = uiDoc.Selection.GetElementIds();
            var lstElementsToCheck = colUserSelectedIds.ToList();

            foreach (var elemId in lstElementsToCheck)
            {
                var fabPart = doc.GetElement(elemId) as FabricationPart;

                if (fabPart == null)
                {
                    var fabAssembly = uiDoc.Document.GetElement(elemId) as AssemblyInstance;

                    if (fabAssembly == null)
                    {
                        colUserSelectedIds.Remove(elemId);
                        continue;
                    }

                    fabPart = doc.GetElement(fabAssembly.GetMemberIds().First()) as FabricationPart;

                    if (fabPart == null)
                    {
                        colUserSelectedIds.Remove(elemId);
                        continue;
                    }
                }
            }

            if (colUserSelectedIds.Count == 0)
            {
                TaskDialog.Show("Disappearing Connectors", "Please select at least one fabrication part.");

                return Result.Cancelled;
            }


            try
            {
                if (!DisconnectTaps(doc, colUserSelectedIds))
                {
                    TaskDialog.Show("Disappearing Connectors", "No taps disconnected, please check selection.");

                    return Result.Failed;
                }

                //set selection set for posted command, not needed for example

                //var exportCommandId = RevitCommandId.LookupCommandId(_exportCommandString);

                //uiApp.PostCommand(exportCommandId);

                uiApp.Idling += revit_Idling;

                _idleFireCount = 0;

                return Result.Succeeded;
            }
            catch (Exception ex)
            {
                TaskDialog.Show("Disappearing Connectors", ex.Message);

                return Result.Failed;
            }
        }

        private bool DisconnectTaps(Document doc, ICollection<ElementId> colSelectedIds)
        {
            var tapsDisconnected = false;

            // Dictionary for attempting to connect tap by re-placing the tap:
            // Key = HostPartElementId Values = HostConnectorID, DistanceToTapConnector, TapElementId, TapConnectorId, TapRoation1, TapRotation2
            _disconnectedTaps = new Dictionary<ElementId, List<Tuple<int, double, ElementId, int, double, double>>>();

            using (var trans = new Transaction(doc, "Disconnect Taps"))
            {
                trans.Start();

                foreach (ElementId elemId in colSelectedIds)
                {
                    var fabPart = doc.GetElement(elemId) as FabricationPart;

                    //(Matt) Skip for now if it is a tap, we want to be from the perspective of the duct/pipe
                    if (fabPart.IsATap() || fabPart.IsAHanger()) continue;

                    var conManager = fabPart.ConnectorManager;

                    foreach (Connector con in conManager.Connectors)
                    {
                        if (!con.IsConnected) continue;

                        var connectedConnector = con.AllRefs
                                                    .OfType<Connector>()
                                                    .First(x => x.IsConnectedTo(con));

                        // as an experiment, i am leaving the tap at host con id 2 in place,
                        // because manually snapping the tap connector back on to the straight
                        // give you a new con id - but apparently if you do it programmatically, it does not
                        // so that's good news.
                        //if (connectedConnector.Id == 5) continue;

                        var connectedPart = connectedConnector.Owner as FabricationPart;

                        if (connectedPart == null) continue;
                        if (!connectedPart.IsATap()) continue;

                        var hostLocationCurve = fabPart.Location as LocationCurve;
                        var hostCenterLine = hostLocationCurve.Curve;

                        var tap = connectedPart;
                        var tapCon = connectedConnector;

                        // For some reason this calculation is not working....
                        //var a = hostCenterLine.Distance(tapCon.Origin); ;
                        //var c = (tapCon.Origin - con.Origin).GetLength();
                        //var b = Math.Sqrt((Math.Pow(c, 2) - Math.Pow(a, 2)));

                        //var distanceToTapCon = b;

                        // So let's try halfway down the straight.
                        var distanceToTapCon = (con.Origin - hostCenterLine.Evaluate(0.5, true)).GetLength();

                        //turns out this is not giving me the halfway point, but it did work as a valid distance.

                        var data = new Tuple<int, double, ElementId, int, double, double>
                                        ( 0, distanceToTapCon, tap.Id, tapCon.Id, 0.0, 0.0);

                        if (_disconnectedTaps.ContainsKey(fabPart.Id))
                        {
                            _disconnectedTaps[fabPart.Id].Add(data);
                        }
                        else
                        {
                            _disconnectedTaps.Add(fabPart.Id, new List<Tuple<int, double, ElementId, int, double, double>>());
                            _disconnectedTaps[fabPart.Id].Add(data);
                        }

                        con.DisconnectFrom(tapCon);

                        tapsDisconnected = true;
                    }
                }

                trans.Commit();
            }

            return tapsDisconnected;
        }

        public void revit_Idling(object sender, Autodesk.Revit.UI.Events.IdlingEventArgs e)
        {
            //(Matt) the posted command doesn't fire until after the first idle
            if (_idleFireCount == 0)
            {
                _idleFireCount += 1;

                return;
            }

            var uiApp = sender as UIApplication;
            var uiDoc = uiApp.ActiveUIDocument;
            var doc = uiDoc.Document;

            try
            {
                ReconnectTaps(doc);
            }
            catch (Exception ex)
            {
                TaskDialog.Show("Disappearing Connectors", ex.Message);
            }
            finally
            {
                uiApp.Idling -= revit_Idling;
            }
        }

        private void ReconnectTaps(Document doc)
        {
            using (var trans = new Transaction(doc, "Reconnect Taps"))
            {
                trans.Start();

                foreach (var entry in _disconnectedTaps)
                {
                    foreach (var data in entry.Value)
                    {
                        var hostPart = doc.GetElement(entry.Key) as FabricationPart;
                        var hostConManager = hostPart.ConnectorManager;
                        var hostConCnt = hostConManager.Connectors.Size;
                        var hostUnusedCnt = hostConManager.UnusedConnectors != null ? hostConManager.UnusedConnectors.Size : 0;
                        var hostCon = hostConManager.Lookup(data.Item1);
                        var distanceToTapCon = data.Item2;
                        var tap = doc.GetElement(data.Item3) as FabricationPart;
                        var tapConManager = tap.ConnectorManager;
                        var tapConCnt = tapConManager.Connectors.Size;
                        var tapUnusedCnt = tapConManager.UnusedConnectors != null ? tapConManager.UnusedConnectors.Size : 0;
                        var tapCon = tapConManager.Lookup(data.Item4);
                        var tapRotation1 = data.Item5;
                        var tapRotation2 = data.Item6;

                        if (hostCon == null)
                            throw new Exception("Unable to reconnect tap, host connector missing:\n\n"
                                              + "Host part id    = " + hostPart.Id.IntegerValue.ToString() + "\n"
                                              + "Host conn id    = " + data.Item1 + "\n"
                                              + "Host conn cnt   = " + hostConCnt + "\n"
                                              + "Host Unused cnt = " + hostUnusedCnt + "\n\n"
                                              + "Tap part id     = " + tap.Id.IntegerValue.ToString() + "\n"
                                              + "Tap conn id     = " + data.Item3 + "\n"
                                              + "Tap conn cnt    = " + tapConCnt + "\n"
                                              + "Tap Unused cnt  = " + tapUnusedCnt);

                        FabricationPart.PlaceAsTap(doc, tapCon, hostCon, distanceToTapCon, tapRotation1, tapRotation2);

                        //if (System.Diagnostics.Debugger.IsAttached)
                        //{
                        //    Debug.Assert(hostConManager.Connectors.Size == hostConCnt + 1, "Tap not connected...");
                        //}

                        //the tap was placed, but without the correct rotations or distance down the straight.
                        //still, this fixes the original problem.
                    }
                }

                trans.Commit();
            }
        }
    }
}
0 Me gusta