private void Compress(List <PmxMorphData> morphList, MultiDictionary <PmxMorphData, VmdMorphFrameData> frameDict)
        {
            var dict = new MultiDictionary <List <int>, PmxMorphData>(new IntegerArrayComparer());

            foreach (var morph in morphList)
            {
                dict.Add(frameDict[morph].ConvertAll(m => m.FrameTime), morph);
            }

            foreach (var key in dict.Keys)
            {
                var removeList = dict[key];

                if (removeList.Count > 1)
                {
                    removeList[0].MorphArray = Compress(removeList);

                    for (int i = 1; i < removeList.Count; i++)
                    {
                        var morph = removeList[i];

                        MorphList.Remove(morph);
                        morphList.Remove(morph);
                    }
                }
            }
        }
Ejemplo n.º 2
0
        public void GetData(PmxModelData data)
        {
            var header = new PmxHeaderData()
            {
                Version = 2.0F
            };

            var boneSlot = new PmxSlotData()
            {
                SlotName = "弾ボーン",
                Type     = PmxSlotData.SLOT_TYPE_BONE,
                Indices  = Enumerable.Range(0, BoneList.Count).ToArray()
            };

            var morphSlot = new PmxSlotData()
            {
                SlotName = "弾モーフ",
                Type     = PmxSlotData.SLOT_TYPE_MORPH,
                Indices  = Enumerable.Range(0, MorphList.Count).ToArray()
            };

            data.Header        = header;
            data.VertexIndices = IndexList.ToArray();
            data.TextureFiles  = TextureList.ToArray();
            data.VertexArray   = VertexList.ToArray();
            data.MaterialArray = MaterialList.ToArray();
            data.BoneArray     = BoneList.ToArray();
            data.MorphArray    = MorphList.ToArray();
            data.SlotArray     = new PmxSlotData[] { boneSlot, morphSlot };
        }
        public void SetupMaterialMorph(ShotProperty prop, PmxMorphData morph, int materialCount, int appliedMaterialCount)
        {
            morph.MorphName = "MO_" + MorphList.Count.ToString();
            morph.SlotType  = MorphSlotType.RIP;
            morph.MorphType = MorphType.MATERIAL;

            morph.MorphArray = new IPmxMorphTypeData[appliedMaterialCount];

            for (int i = 0; i < appliedMaterialCount; i++)
            {
                morph.MorphArray[i] = new PmxMorphMaterialData()
                {
                    CalcType      = 0,
                    Ambient       = new Vector3(1, 1, 1),
                    Diffuse       = new Vector4(1, 1, 1, 0),
                    Specular      = new Vector3(1, 1, 1),
                    Shininess     = 1.0F,
                    Edge          = new Vector4(1, 1, 1, 1),
                    EdgeThick     = 1.0F,
                    Texture       = new Vector4(1, 1, 1, 1),
                    SphereTexture = new Vector4(1, 1, 1, 1),
                    ToonTexture   = new Vector4(1, 1, 1, 1),
                };
            }

            for (int i = 0; i < appliedMaterialCount; i++)
            {
                morph.MorphArray[i].Index = materialCount + i;
            }
            MorphList.Add(morph);
        }
Ejemplo n.º 4
0
        public bool FromText(string text)
        {
            bool result = false;

            try
            {
                if (!IsVpdText(text))
                {
                    return(result);
                }
                Match  match = new Regex(InfoGetReg, RegexOptions.IgnoreCase).Match(text);
                string text2 = "";
                if (match.Success)
                {
                    text2 = match.Groups["name"].Value;
                    if (text2.ToLower().Contains(NameExt))
                    {
                        text2 = text2.Replace(NameExt, "");
                    }
                    ModelName = text2;
                }
                PoseList.Clear();
                match = new Regex(BoneGetReg, RegexOptions.IgnoreCase).Match(text);
                while (match.Success)
                {
                    string     text3    = "";
                    Vector3    t        = new Vector3(0f, 0f, 0f);
                    Quaternion identity = Quaternion.Identity;
                    text3 = match.Groups["name"].Value;
                    float.TryParse(match.Groups["trans_x"].Value, out t.x);
                    float.TryParse(match.Groups["trans_y"].Value, out t.y);
                    float.TryParse(match.Groups["trans_z"].Value, out t.z);
                    float.TryParse(match.Groups["rot_x"].Value, out identity.x);
                    float.TryParse(match.Groups["rot_y"].Value, out identity.y);
                    float.TryParse(match.Groups["rot_z"].Value, out identity.z);
                    float.TryParse(match.Groups["rot_w"].Value, out identity.w);
                    PoseList.Add(new PoseData(text3, identity, t));
                    match = match.NextMatch();
                }
                MorphList.Clear();
                match = new Regex(MorphGetReg, RegexOptions.IgnoreCase).Match(text);
                while (match.Success)
                {
                    string text4   = "";
                    float  result2 = 0f;
                    text4 = match.Groups["name"].Value;
                    float.TryParse(match.Groups["val"].Value, out result2);
                    MorphList.Add(new MorphData(text4, result2));
                    match = match.NextMatch();
                }
                result = true;
                return(result);
            }
            catch (Exception)
            {
                return(result);
            }
        }
Ejemplo n.º 5
0
        private void CreateNode(Task task)
        {
            var node = MorphList.CreateItem(MorphList.GetRoot());

            node.SetCellMode(0, TreeItem.TreeCellMode.String);
            node.SetText(0, task.Key);

            node.SetCellMode(1, TreeItem.TreeCellMode.String);
            node.SetText(1, task.Surface);

            node.SetCellMode(2, TreeItem.TreeCellMode.String);
            node.SetText(2, Translate($"ui.BlendMapGenerator.status.{task.State}"));

            node.SetCellMode(3, TreeItem.TreeCellMode.String);
            node.SetText(3, task.File.Name);
        }
Ejemplo n.º 6
0
 public virtual void Clear()
 {
     Header.ElementFormat.Ver      = 2.1f;
     Header.ElementFormat.UVACount = 0;
     ModelInfo.Clear();
     VertexList.Clear();
     FaceList.Clear();
     MaterialList.Clear();
     BoneList.Clear();
     MorphList.Clear();
     BodyList.Clear();
     JointList.Clear();
     SoftBodyList.Clear();
     InitializeSystemNode();
     FilePath     = "";
     LoadErrCount = 0;
 }
Ejemplo n.º 7
0
 protected override Validation <string, BlendMapGenerator> CreateService(
     Godot.Control node, ILoggerFactory loggerFactory)
 {
     return
         (from inputEdit in InputEdit
          .ToValidation("Failed to find the edit control for the input directory.")
          from outputEdit in OutputEdit
          .ToValidation("Failed to find the edit control for the output directory.")
          from sourceList in SourceList
          .ToValidation("Failed to find the source item control.")
          from morphList in MorphList
          .ToValidation("Failed to find the morph item control.")
          from progressLabel in ProgressLabel
          .ToValidation("Failed to find the progress label.")
          from progressBar in ProgressBar
          .ToValidation("Failed to find the progress bar.")
          from inputButton in InputButton
          .ToValidation("Failed to find the button for the input chooser dialog.")
          from outputButton in OutputButton
          .ToValidation("Failed to find the button for the output chooser dialog.")
          from startButton in StartButton
          .ToValidation("Failed to find the start button.")
          from closeButton in CloseButton
          .ToValidation("Failed to find the close button.")
          from fileDialog in FileDialog
          .ToValidation("Failed to find the file chooser dialog.")
          from infoLabel in InfoLabel
          .ToValidation("Failed to find the information label.")
          select new BlendMapGenerator(
              inputEdit,
              outputEdit,
              sourceList,
              morphList,
              progressLabel,
              progressBar,
              inputButton,
              outputButton,
              startButton,
              closeButton,
              fileDialog,
              infoLabel,
              node,
              loggerFactory));
 }
Ejemplo n.º 8
0
        private void Start(Lst <MeshSet> meshSets)
        {
            var tasks = meshSets.Bind(m => m.Tasks).Filter(t => t.State != TaskState.UpToDate).Freeze();

            ProgressBar.Value    = 0;
            ProgressBar.MaxValue = tasks.Count();

            var process = tasks.ToObservable()
                          .TakeUntil(Disposed.Where(identity))
                          .ObserveOn(Scheduler.Default)
                          .Do(t => t.Run(this, LoggerFactory))
                          .Do(t =>
            {
                var selected = Optional(SourceList.GetSelected()).Map(i => i.GetText(0));

                if (selected.Contains(t.Parent.Key))
                {
                    MorphList.GetRoot()
                    .Children()
                    .Find(i => i.GetText(0) == t.Key && i.GetText(1) == t.Surface)
                    .Iter(n => n.SetText(2, Translate($"ui.BlendMapGenerator.status.{t.State}")));
                }
            })
                          .Publish();

            process
            .SubscribeOn(Node.GetScheduler())
            .Subscribe(_ => ProgressBar.Value += 1, this);

            var running = process.Select(_ => false).TakeLast(1).StartWith(tasks.Any());

            running
            .SubscribeOn(Node.GetScheduler())
            .Subscribe(v =>
            {
                StartButton.Disabled  = v;
                InputButton.Disabled  = v;
                InputEdit.Editable    = !v;
                OutputButton.Disabled = v;
                OutputEdit.Editable   = !v;
            }, this);

            process.Connect();
        }
Ejemplo n.º 9
0
        private void Compress(List <PmxMorphData> morphList, MultiDictionary <PmxMorphData, VmdMorphFrameData> frameDataDict)
        {
            var dict = new MultiDictionary <int[], PmxMorphData>(new IntegerArrayComparer());

            foreach (var morph in morphList)
            {
                dict.Add(Array.ConvertAll(frameDataDict[morph].ToArray(), m => m.KeyFrameNo), morph);
            }

            foreach (var key in dict.Keys)
            {
                var removeList = dict[key];

                if (removeList.Count > 1)
                {
                    removeList[0].MorphArray = Compress(removeList);

                    for (int i = 1; i < removeList.Count; i++)
                    {
                        var morph = removeList[i];

                        MorphList.Remove(morph);
                        World.VmdMotion.MorphDict.Remove(morph);
                    }
                }
            }

            IPmxMorphTypeData[] Compress(List <PmxMorphData> list)
            {
                var morphTypeDataList = new List <IPmxMorphTypeData>();

                foreach (var morph in list)
                {
                    morphTypeDataList.AddRange(morph.MorphArray);
                }
                return(morphTypeDataList.ToArray());
            }
        }
Ejemplo n.º 10
0
        private void SetupShotModelData(ShotModelData data)
        {
            int[] indices = Array.ConvertAll(data.Indices, i => i + VertexList.Count);
            IndexList.AddRange(indices);

            PmxVertexData[] vertices = data.Vertices;
            foreach (var vertex in vertices)
            {
                vertex.VertexId = VertexList.Count;
                vertex.BoneId   = Array.ConvertAll(vertex.BoneId, i => i + BoneList.Count);
                VertexList.Add(vertex);
            }

            PmxMaterialData[] materials = data.Materials;
            PmxMorphData      morph     = data.MaterialMorph;

            morph.MorphName  = data.Property.Type.Name[0] + MorphList.Count.ToString();
            morph.Type       = 4;
            morph.MorphArray = ArrayUtil.Set(new PmxMorphMaterialData[materials.Length], i => new PmxMorphMaterialData());

            for (int i = 0; i < materials.Length; i++)
            {
                morph.MorphArray[i].Index = MaterialList.Count + i;
                morph.MorphId             = MorphList.Count + i;
            }
            MorphList.Add(morph);

            string[] textures = data.Textures;
            foreach (var texture in textures)
            {
                if (!TextureList.Contains(texture))
                {
                    TextureList.Add(texture);
                }
            }

            foreach (PmxMaterialData material in materials)
            {
                material.MaterialName = data.Property.Type.Name[0] + MaterialList.Count.ToString();

                if (0 <= material.TextureId && material.TextureId < textures.Length)
                {
                    material.TextureId = TextureList.IndexOf(textures[material.TextureId]);
                }
                else
                {
                    material.TextureId = -1;
                }

                if (0 <= material.SphereId && material.SphereId < textures.Length)
                {
                    material.SphereId = TextureList.IndexOf(textures[material.SphereId]);
                }
                else
                {
                    material.SphereId = -1;
                }
                material.MaterialId = MaterialList.Count;
                MaterialList.Add(material);
            }
            SetupBone(data, data.Bones);

            ModelDataList.Add(data);
        }
Ejemplo n.º 11
0
        public virtual void ToStream(Stream s, bool id)
        {
            PmxHeader header = Header;

            header.ElementFormat.WithID = id;
            PmxTextureTable pmxTextureTable = new PmxTextureTable(MaterialList);

            UpdateElementFormatSize(header.ElementFormat, pmxTextureTable);
            header.ToStreamEx(s);
            ModelInfo.ToStreamEx(s, header.ElementFormat);
            PmxStreamHelper.WriteElement_Int32(s, VertexList.Count);
            for (int i = 0; i < VertexList.Count; i++)
            {
                VertexList[i].ToStreamEx(s, header.ElementFormat);
            }
            PmxStreamHelper.WriteElement_Int32(s, FaceList.Count);
            for (int j = 0; j < FaceList.Count; j++)
            {
                PmxStreamHelper.WriteElement_Int32(s, FaceList[j], header.ElementFormat.VertexSize, signed: false);
            }
            pmxTextureTable.ToStreamEx(s, header.ElementFormat);
            PmxStreamHelper.WriteElement_Int32(s, MaterialList.Count);
            for (int k = 0; k < MaterialList.Count; k++)
            {
                MaterialList[k].ToStreamEx_TexTable(s, pmxTextureTable, header.ElementFormat);
            }
            PmxStreamHelper.WriteElement_Int32(s, BoneList.Count);
            for (int l = 0; l < BoneList.Count; l++)
            {
                BoneList[l].ToStreamEx(s, header.ElementFormat);
            }
            if (header.Ver < 2.1f)
            {
                int num = MorphList.Where((PmxMorph mp) => mp.IsImpulse).Count();
                PmxStreamHelper.WriteElement_Int32(s, MorphList.Count - num);
            }
            else
            {
                PmxStreamHelper.WriteElement_Int32(s, MorphList.Count);
            }
            for (int m = 0; m < MorphList.Count; m++)
            {
                MorphList[m].ToStreamEx(s, header.ElementFormat);
            }
            PmxStreamHelper.WriteElement_Int32(s, NodeList.Count);
            for (int n = 0; n < NodeList.Count; n++)
            {
                NodeList[n].ToStreamEx(s, header.ElementFormat);
            }
            PmxStreamHelper.WriteElement_Int32(s, BodyList.Count);
            for (int num2 = 0; num2 < BodyList.Count; num2++)
            {
                BodyList[num2].ToStreamEx(s, header.ElementFormat);
            }
            PmxStreamHelper.WriteElement_Int32(s, JointList.Count);
            for (int num3 = 0; num3 < JointList.Count; num3++)
            {
                JointList[num3].ToStreamEx(s, header.ElementFormat);
            }
            if (header.Ver >= 2.1f)
            {
                PmxStreamHelper.WriteElement_Int32(s, SoftBodyList.Count);
                for (int num4 = 0; num4 < SoftBodyList.Count; num4++)
                {
                    SoftBodyList[num4].ToStreamEx(s, header.ElementFormat);
                }
            }
            if (id)
            {
                PmxStreamHelper.WriteString(s, FilePath, header.ElementFormat);
            }
            header.ElementFormat.WithID = false;
        }
Ejemplo n.º 12
0
        public virtual void FromStream(Stream s, bool id)
        {
            Action <Action> action = delegate(Action a)
            {
                try
                {
                    a();
                }
                catch (Exception)
                {
                    LoadErrCount++;
                }
            };
            PmxHeader head = new PmxHeader();

            head.FromStreamEx(s);
            Header.FromHeader(head);
            head.ElementFormat.WithID = id;
            action(delegate
            {
                ModelInfo.FromStreamEx(s, head.ElementFormat);
            });
            int count = 0;

            action(delegate
            {
                count = PmxStreamHelper.ReadElement_Int32(s);
            });
            VertexList.Clear();
            VertexList.Capacity = count;
            for (int k = 0; k < count; k++)
            {
                PmxVertex v = new PmxVertex();
                action(delegate
                {
                    v.FromStreamEx(s, head.ElementFormat);
                });
                VertexList.Add(v);
            }
            action(delegate
            {
                count = PmxStreamHelper.ReadElement_Int32(s);
            });
            FaceList.Clear();
            FaceList.Capacity = count;
            for (int l = 0; l < count; l++)
            {
                int ix = 0;
                action(delegate
                {
                    ix = PmxStreamHelper.ReadElement_Int32(s, head.ElementFormat.VertexSize, signed: false);
                });
                FaceList.Add(ix);
            }
            PmxTextureTable tx = new PmxTextureTable();

            action(delegate
            {
                tx.FromStreamEx(s, head.ElementFormat);
            });
            action(delegate
            {
                count = PmxStreamHelper.ReadElement_Int32(s);
            });
            MaterialList.Clear();
            MaterialList.Capacity = count;
            for (int m = 0; m < count; m++)
            {
                PmxMaterial j = new PmxMaterial();
                action(delegate
                {
                    j.FromStreamEx_TexTable(s, tx, head.ElementFormat);
                });
                MaterialList.Add(j);
            }
            action(delegate
            {
                count = PmxStreamHelper.ReadElement_Int32(s);
            });
            BoneList.Clear();
            BoneList.Capacity = count;
            for (int n = 0; n < count; n++)
            {
                PmxBone b = new PmxBone();
                action(delegate
                {
                    b.FromStreamEx(s, head.ElementFormat);
                });
                BoneList.Add(b);
            }
            action(delegate
            {
                count = PmxStreamHelper.ReadElement_Int32(s);
            });
            MorphList.Clear();
            MorphList.Capacity = count;
            for (int num = 0; num < count; num++)
            {
                PmxMorph morph = new PmxMorph();
                action(delegate
                {
                    morph.FromStreamEx(s, head.ElementFormat);
                });
                MorphList.Add(morph);
            }
            action(delegate
            {
                count = PmxStreamHelper.ReadElement_Int32(s);
            });
            NodeList.Clear();
            NodeList.Capacity = count;
            for (int num2 = 0; num2 < count; num2++)
            {
                PmxNode node = new PmxNode();
                action(delegate
                {
                    node.FromStreamEx(s, head.ElementFormat);
                });
                NodeList.Add(node);
                if (NodeList[num2].SystemNode)
                {
                    if (NodeList[num2].Name == "Root")
                    {
                        RootNode = NodeList[num2];
                    }
                    else if (NodeList[num2].Name == "表情")
                    {
                        ExpNode = NodeList[num2];
                    }
                }
            }
            action(delegate
            {
                count = PmxStreamHelper.ReadElement_Int32(s);
            });
            BodyList.Clear();
            BodyList.Capacity = count;
            for (int num3 = 0; num3 < count; num3++)
            {
                PmxBody b2 = new PmxBody();
                action(delegate
                {
                    b2.FromStreamEx(s, head.ElementFormat);
                });
                BodyList.Add(b2);
            }
            action(delegate
            {
                count = PmxStreamHelper.ReadElement_Int32(s);
            });
            JointList.Clear();
            JointList.Capacity = count;
            for (int num4 = 0; num4 < count; num4++)
            {
                PmxJoint i = new PmxJoint();
                action(delegate
                {
                    i.FromStreamEx(s, head.ElementFormat);
                });
                JointList.Add(i);
            }
            if (head.Ver >= 2.1f)
            {
                action(delegate
                {
                    count = PmxStreamHelper.ReadElement_Int32(s);
                });
                SoftBodyList.Clear();
                SoftBodyList.Capacity = count;
                for (int num5 = 0; num5 < count; num5++)
                {
                    PmxSoftBody b3 = new PmxSoftBody();
                    action(delegate
                    {
                        b3.FromStreamEx(s, head.ElementFormat);
                    });
                    SoftBodyList.Add(b3);
                }
            }
            if (id)
            {
                action(delegate
                {
                    FilePath = PmxStreamHelper.ReadString(s, head.ElementFormat);
                });
            }
            head.ElementFormat.WithID = false;
        }
Ejemplo n.º 13
0
        public void FromPmx(Pmx pmx)
        {
            Clear();
            FilePath     = pmx.FilePath;
            LoadErrCount = pmx.LoadErrCount;
            Header       = pmx.Header.Clone();
            ModelInfo    = pmx.ModelInfo.Clone();
            int count = pmx.VertexList.Count;

            VertexList.Capacity = count;
            for (int i = 0; i < count; i++)
            {
                VertexList.Add(pmx.VertexList[i].Clone());
            }
            count             = pmx.FaceList.Count;
            FaceList.Capacity = count;
            for (int j = 0; j < count; j++)
            {
                FaceList.Add(pmx.FaceList[j]);
            }
            count = pmx.MaterialList.Count;
            MaterialList.Capacity = count;
            for (int k = 0; k < count; k++)
            {
                MaterialList.Add(pmx.MaterialList[k].Clone());
            }
            count             = pmx.BoneList.Count;
            BoneList.Capacity = count;
            for (int l = 0; l < count; l++)
            {
                BoneList.Add(pmx.BoneList[l].Clone());
            }
            count = pmx.MorphList.Count;
            MorphList.Capacity = count;
            for (int m = 0; m < count; m++)
            {
                MorphList.Add(pmx.MorphList[m].Clone());
            }
            count = pmx.NodeList.Count;
            NodeList.Clear();
            NodeList.Capacity = count;
            for (int n = 0; n < count; n++)
            {
                NodeList.Add(pmx.NodeList[n].Clone());
                if (NodeList[n].SystemNode)
                {
                    if (NodeList[n].Name == "Root")
                    {
                        RootNode = NodeList[n];
                    }
                    else if (NodeList[n].Name == "表情")
                    {
                        ExpNode = NodeList[n];
                    }
                }
            }
            count             = pmx.BodyList.Count;
            BodyList.Capacity = count;
            for (int num = 0; num < count; num++)
            {
                BodyList.Add(pmx.BodyList[num].Clone());
            }
            count = pmx.JointList.Count;
            JointList.Capacity = count;
            for (int num2 = 0; num2 < count; num2++)
            {
                JointList.Add(pmx.JointList[num2].Clone());
            }
            count = pmx.SoftBodyList.Count;
            SoftBodyList.Capacity = count;
            for (int num3 = 0; num3 < count; num3++)
            {
                SoftBodyList.Add(pmx.SoftBodyList[num3].Clone());
            }
        }
Ejemplo n.º 14
0
        protected override void PostConstruct()
        {
            base.PostConstruct();

            SourceList.CreateItem();

            SourceList.SetColumnTitle(0, Translate("ui.BlendMapGenerator.name"));
            SourceList.SetColumnTitle(1, Translate("ui.BlendMapGenerator.path"));

            SourceList.SetColumnTitlesVisible(true);

            MorphList.CreateItem();

            MorphList.SetColumnTitle(0, Translate("ui.BlendMapGenerator.name"));
            MorphList.SetColumnTitle(1, Translate("ui.BlendMapGenerator.surface"));
            MorphList.SetColumnTitle(2, Translate("ui.BlendMapGenerator.status"));
            MorphList.SetColumnTitle(3, Translate("ui.BlendMapGenerator.path"));

            MorphList.SetColumnTitlesVisible(true);

            FileDialog.CurrentDir = "res://";

            var disposed = Disposed.Where(identity);

            IObservable <Option <DirectoryInfo> > ObserveDirectoryChange(
                Button button,
                LineEdit edit,
                // This is a horrible workaround for a problem that Popup.popup_hide never gets fired.
                IObservable <Unit> anotherButtonPressed)
            {
                Option <DirectoryInfo> Validate(string path)
                {
                    var dir = new DirectoryInfo(path);

                    return(dir.IsDirectory && dir.Exists ? Some(dir) : None);
                }

                var fromChooser = button.OnPress()
                                  .Do(_ =>
                {
                    FileDialog.Mode = ModeEnum.OpenDir;
                    FileDialog.ShowModal(true);
                })
                                  .Select(_ => FileDialog.OnSelectDirectory().Select(Some).TakeUntil(anotherButtonPressed))
                                  .Switch()
                                  .Do(dir => edit.Text = dir.Map(v => v.Path).IfNone(""));

                var fromEdit = edit.OnTextChanged()
                               .Select(Validate)
                               .Do(dir => dir.Iter(v => FileDialog.CurrentDir = v.Path));

                return(fromChooser.Merge(fromEdit));
            }

            var onInputChange  = ObserveDirectoryChange(InputButton, InputEdit, OutputButton.OnPress());
            var onOutputChange = ObserveDirectoryChange(OutputButton, OutputEdit, InputButton.OnPress());

            IObservable <Option <string> > ToErrorMessage(IObservable <Option <DirectoryInfo> > value, string msg) =>
            value.StartWith(None).Select(v => v.IsSome ? None : Some(msg));

            var message = Observable.CombineLatest(
                ToErrorMessage(onInputChange, Translate("error.invalid.directory.input")),
                ToErrorMessage(onOutputChange, Translate("error.invalid.directory.output")),
                (m1, m2) => m1.Concat(m2).HeadOrNone().IfNone("")
                ).Skip(1);

            message
            .TakeUntil(disposed)
            .Subscribe(m => InfoLabel.Text = m, this);

            var onDirectoryChange = Observable.CombineLatest(
                onInputChange.Select(v => v.ToObservable()).Switch(),
                onOutputChange.Select(v => v.ToObservable()).Switch(),
                (input, output) => new Paths(input, output));

            var meshes = onDirectoryChange
                         .Select(v => v.Input.Contents)
                         .Select(FindMeshes)
                         .Select(v => v.Cast <FileInfo>().Freeze());

            var meshSets = meshes.CombineLatest(onDirectoryChange, (files, paths) => (files, paths))
                           .Do(v => Logger.LogDebug("Searching for base meshes in '{}'.", v.paths.Input.Path))
                           .Select(v => v.files.Bind(f => MeshSet.TryCreate(f, v.paths, v.files, Logger)))
                           .Select(v => v.Freeze());

            var shownTasks = SourceList.OnItemSelect()
                             .Select(v => v.Map(i => i.GetText(0)))
                             .WithLatestFrom(meshSets, (key, s) => s.Find(v => v.Key == key).Bind(v => v.Tasks));

            var valid = meshSets.Select(set => set.Bind(v => v.Tasks).Any(t => t.State != TaskState.UpToDate));

            meshSets
            .Throttle(TimeSpan.FromSeconds(1))
            .TakeUntil(disposed)
            .Do(_ => SourceList.RemoveAllNodes())
            .Do(v => v.Iter(CreateNode))
            .Do(_ => SourceList.GetRoot().Children().HeadOrNone().Iter(c => c.Select(0)))
            .Subscribe(this);

            shownTasks
            .TakeUntil(disposed)
            .Do(_ => MorphList.RemoveAllNodes())
            .Subscribe(v => v.Iter(CreateNode), this);

            valid
            .TakeUntil(disposed)
            .Subscribe(v => StartButton.Disabled = !v, this);

            StartButton.OnPress()
            .WithLatestFrom(meshSets, (_, v) => v)
            .TakeUntil(disposed)
            .Subscribe(Start, this);

            CloseButton.OnPress()
            .TakeUntil(disposed)
            .Subscribe(_ => Quit(), this);
        }