// The dictionary is indexed by part id (flight or craft) such that
        // the symmetry set can be found by the id of any part within the set.
        // However, the sets consist of only those parts that hold resources.
        void FindSymmetrySets(IEnumerable <Part> parts)
        {
            visitedParts.Clear();
            var dict = new Dictionary <uint, RMResourceSet> ();
            var sets = new List <RMResourceSet> ();

            foreach (Part p in parts)
            {
                if (excludeParts.Contains(p))
                {
                    continue;
                }
                uint id = GetID(p);
                //Debug.LogFormat ("{0} {1} {2}", id, p.name, p.symmetryCounterparts.Count);
                if (p.Resources.Count < 1)
                {
                    // no resources, so no point worrying about symmetry
                    continue;
                }
                if (p.symmetryCounterparts.Count < 1)
                {
                    // not part of a symmetry set
                    continue;
                }
                if (visitedParts.Contains(id))
                {
                    // already added this part
                    continue;
                }
                visitedParts.Add(id);
                RMResourceSet symmetrySet = new RMResourceSet();
                symmetrySet.balanced = true;
                symmetrySet.name     = "sym " + id.ToString();
                symmetrySet.AddPart(p);
                dict[id] = symmetrySet;
                sets.Add(symmetrySet);
                for (int j = 0; j < p.symmetryCounterparts.Count; j++)
                {
                    Part s = p.symmetryCounterparts[j];
                    id = GetID(s);
                    visitedParts.Add(id);
                    symmetrySet.AddPart(s);
                    dict[id] = symmetrySet;
                }
            }
            symmetryDict = dict;
            symmetrySets = sets;
        }
        void ProcessParts(Part part, RMResourceSet set)
        {
            var cp = ConnectedParts(part);

            if (part.parent != null && cp.Has(part.parent))
            {
                //Debug.LogFormat("[RMResourceManager] ProcessParts: parent {0}", part.parent);
                set = AddModule(cp.Name(part.parent), GetID(part.parent));
            }

            set.AddPart(part);

            for (int i = part.children.Count; i-- > 0;)
            {
                var child = part.children[i];
                if (excludeParts.Contains(child))
                {
                    continue;
                }
                if (cp.Has(child))
                {
                    //Debug.LogFormat("[RMResourceManager] ProcessParts: child {0}", child);
                    ProcessParts(child, AddModule(cp.Name(child), GetID(child)));
                }
                else
                {
                    ProcessParts(child, set);
                }
            }
        }
        void FinalizeResourceSets()
        {
            visitedParts.Clear();
            resourceSets = new List <RMResourceSet> ();
            masterSet    = new RMResourceSet();
            RMResourceSet set = null;

            foreach (var m in moduleSets)
            {
                if (set == null)
                {
                    set      = new RMResourceSet();
                    set.name = m.name;
                    set.id   = m.id;
                }
                foreach (var p in m.parts)
                {
                    uint id = GetID(p);
                    if (visitedParts.Contains(id))
                    {
                        continue;
                    }
                    if (p.symmetryCounterparts.Count > 0 &&
                        symmetryDict.ContainsKey(id))
                    {
                        RMResourceSet sym = symmetryDict[id];
                        foreach (var s in sym.parts)
                        {
                            uint sid = GetID(s);
                            visitedParts.Add(sid);
                        }
                        set.AddSet(sym);
                        masterSet.AddSet(sym);
                    }
                    else
                    {
                        visitedParts.Contains(id);
                        set.AddPart(p);
                        masterSet.AddPart(p);
                    }
                }
                if (set.parts.Count > 0 || set.sets.Count > 0)
                {
                    resourceSets.Add(set);
                    set = null;
                }
            }
        }