private void DoWork([NotNull] object state) { try { DoWorkInternal(state); Log("Done."); } catch (Exception ex) { var exDesc = ex.ToString(); Log("Error occurred."); Log(exDesc); InvokeInForm(() => { MessageBox.Show(exDesc, ApplicationHelper.GetApplicationTitle(), MessageBoxButtons.OK, MessageBoxIcon.Error); }); } finally { InvokeInForm(() => _form.EnableMainControls(true)); } }
private void BtnGo_Click(object sender, EventArgs e) { void Alert(string text) { MessageBox.Show(text, ApplicationHelper.GetApplicationTitle(), MessageBoxButtons.OK, MessageBoxIcon.Warning); } bool Confirm(string text) { var r = MessageBox.Show(text, ApplicationHelper.GetApplicationTitle(), MessageBoxButtons.YesNo, MessageBoxIcon.Warning); return(r == DialogResult.Yes); } bool CheckInputParams() { bool CheckOutputDir(string filePath) { var outputDirExists = !string.IsNullOrEmpty(filePath); if (outputDirExists) { var file = new FileInfo(filePath); var dir = file.Directory; outputDirExists = dir != null && dir.Exists; } if (!outputDirExists) { Alert($"Output folder of \"{filePath}\" does not exist."); } return(outputDirExists); } if (!File.Exists(txtInputHead.Text)) { Alert($"Head model \"{txtInputHead.Text}\" does not exist."); return(false); } if (!File.Exists(txtInputBody.Text)) { Alert($"Body model \"{txtInputBody.Text}\" does not exist."); return(false); } if (chkGenerateModel.Checked) { if (!CheckOutputDir(txtOutputModel.Text)) { return(false); } } if (chkOptApplyCharHeight.Checked) { if (!float.TryParse(txtOptCharHeight.Text, NumberStyles.Float, GlobalizationHelper.Culture, out var height) || height <= 0.1f) { Alert("Please enter a valid idol height."); } if (height < 1.0f || 2.0f < height) { var heightStr = height.ToString(GlobalizationHelper.Culture); // Don't mind multiple string concatenations. It only executes once. :D var message = "You entered a strange value for idol height." + " This may be caused by the differences of decimal point among countries." + " A wrong value may let this program generate wrong mesh and bones for the model." + Environment.NewLine + Environment.NewLine + $"The number in the numeric format of your culture ({GlobalizationHelper.Culture.DisplayName}) is: {heightStr}" + Environment.NewLine + Environment.NewLine + "Are you sure that you entered a correct value?"; if (!Confirm(message)) { return(false); } } } if (chkGenerateCharAnim.Checked) { if (!File.Exists(txtInputDance.Text)) { Alert($"Dance data \"{txtInputDance.Text}\" does not exist."); return(false); } if (!File.Exists(txtInputFacialExpression.Text)) { Alert($"Facial expression data \"{txtInputFacialExpression.Text}\" does not exist."); return(false); } if (!CheckOutputDir(txtOutputCharAnim.Text)) { return(false); } if (!File.Exists(txtInputFacialExpression.Text)) { Alert($"Facial expression mapping file \"{txtOptFEMappings.Text}\" does not exist."); return(false); } } if (chkGenerateCameraMotion.Checked) { if (!File.Exists(txtInputCamera.Text)) { Alert($"Camera data \"{txtInputCamera.Text}\" does not exist."); return(false); } if (!CheckOutputDir(txtOutputCameraMotion.Text)) { return(false); } } if (radOptCamFormatVmd.Checked) { // globalization: The type is uint32 so only thousand separator appears. In either way, // it will raise a parse error if the user does not conform the UI locale (e.g. input // "12,345" in French locale, or "12.345" in US locale). if (!uint.TryParse(txtOptFixedFov.Text, NumberStyles.Integer, GlobalizationHelper.Culture, out _)) { Alert($"FOV value \"{txtOptFixedFov.Text}\" should be a valid positive integer."); return(false); } } if (!Regex.IsMatch(txtInputHead.Text, @"ch_[a-z]{2}\d{3}_(?:\d{3}[a-z]{3}|[a-z])\.unity3d$", RegexOptions.CultureInvariant)) { Alert($"File \"{txtInputHead.Text}\" does not look like a character head file from the game."); return(false); } if (!Regex.IsMatch(txtInputBody.Text, @"cb_[a-z]{2}\d{3}_(?:\d{3}[a-z]{3}|[a-z])\.unity3d$", RegexOptions.CultureInvariant)) { Alert($"File \"{txtInputBody.Text}\" does not look like a character body file from the game."); return(false); } if (chkGenerateCharAnim.Checked) { if (!Regex.IsMatch(txtInputDance.Text, @"dan_[a-z0-9]{6}_0[12345]\.imo\.unity3d$", RegexOptions.CultureInvariant)) { Alert($"File \"{txtInputDance.Text}\" does not look like a dance data file from the game."); return(false); } if (!Regex.IsMatch(txtInputFacialExpression.Text, @"scrobj_[a-z0-9]{6}\.unity3d$", RegexOptions.CultureInvariant)) { Alert($"File \"{txtInputFacialExpression.Text}\" does not look like a mixed data file from the game containing facial expressions."); return(false); } } if (chkGenerateCameraMotion.Checked) { if (!Regex.IsMatch(txtInputCamera.Text, @"cam_[a-z0-9]{6}\.imo\.unity3d$", RegexOptions.CultureInvariant)) { Alert($"File \"{txtInputCamera.Text}\" does not look like a camera data file from the game."); return(false); } if (radOptCamFormatVmd.Checked) { if (!txtOutputCameraMotion.Text.EndsWith(".vmd", StringComparison.OrdinalIgnoreCase)) { if (!Confirm("The output camera file name does not ends with \".vmd\". Are you sure to continue?")) { return(false); } } } else if (radOptCamFormatMvd.Checked) { if (!txtOutputCameraMotion.Text.EndsWith(".mvd", StringComparison.OrdinalIgnoreCase)) { if (!Confirm("The output camera file name does not ends with \".mvd\". Are you sure to continue?")) { return(false); } } } } return(true); } InputParams PrepareInputParams() { var ip = new InputParams(); ip.GenerateModel = chkGenerateModel.Checked; ip.GenerateCharacterMotion = chkGenerateCharAnim.Checked; ip.GenerateCameraMotion = chkGenerateCameraMotion.Checked; ip.InputHead = txtInputHead.Text; ip.InputBody = txtInputBody.Text; ip.InputDance = txtInputDance.Text; ip.InputFacialExpression = txtInputFacialExpression.Text; ip.InputCamera = txtInputCamera.Text; ip.OutputModel = txtOutputModel.Text; ip.OutputCharacterAnimation = txtOutputCharAnim.Text; ip.OutputCamera = txtOutputCameraMotion.Text; ip.MotionSource = radOptMotionSourceMltd.Checked ? MotionFormat.Mltd : MotionFormat.Mmd; ip.ScalePmx = chkOptScalePmx.Checked; ip.ConsiderIdolHeight = chkOptApplyCharHeight.Checked; ip.IdolHeight = ip.ConsiderIdolHeight ? Convert.ToSingle(txtOptCharHeight.Text) : 0; ip.TranslateBoneNames = chkOptTranslateBoneNames.Checked; ip.AppendLegIkBones = chkOptAppendLegIKBones.Checked; ip.FixCenterBones = chkOptFixCenterBones.Checked; ip.ConvertBindingPose = chkOptConvertToTdaPose.Checked; ip.AppendEyeBones = chkOptAppendEyeBones.Checked; ip.HideUnityGeneratedBones = chkOptHideUnityGenBones.Checked; ip.TranslateFacialExpressionNames = chkOptTranslateFacialExpressionNames.Checked; ip.ImportPhysics = chkOptImportPhysics.Checked; ip.TransformTo30Fps = radOptAnimFrameRate30.Checked; ip.ScaleVmd = chkOptScaleVmd.Checked; ip.UseMvd = radOptCamFormatMvd.Checked; ip.FixedFov = ip.UseMvd ? Convert.ToUInt32(txtOptFixedFov.Text) : 0; ip.SongPosition = cboOptSongPosition.SelectedIndex + 1; ip.FacialExpressionMappingFilePath = txtOptFEMappings.Text; return(ip); } InputParams p; try { if (!CheckInputParams()) { return; } p = PrepareInputParams(); } catch (Exception ex) { Alert(ex.Message); return; } var thread = new Thread(DoWork); thread.Name = "Conversion thread"; thread.IsBackground = true; thread.Start(p); EnableMainControls(false); }
private void BtnGo_Click(object sender, EventArgs e) { void Alert(string text) { MessageBox.Show(text, ApplicationHelper.GetApplicationTitle(), MessageBoxButtons.OK, MessageBoxIcon.Warning); } bool Confirm(string text) { var r = MessageBox.Show(text, ApplicationHelper.GetApplicationTitle(), MessageBoxButtons.YesNo, MessageBoxIcon.Warning); return(r == DialogResult.Yes); } bool CheckInputParams() { bool CheckOutputDir(string filePath) { var outputDirExists = !string.IsNullOrEmpty(filePath); if (outputDirExists) { var file = new FileInfo(filePath); var dir = file.Directory; outputDirExists = dir != null && dir.Exists; } if (!outputDirExists) { Alert($"Output folder of \"{filePath}\" does not exist."); } return(outputDirExists); } if (chkGenerateModel.Checked || chkGenerateCharAnim.Checked) { if (!File.Exists(txtInputHead.Text)) { Alert($"Head model \"{txtInputHead.Text}\" does not exist."); return(false); } if (!File.Exists(txtInputBody.Text)) { Alert($"Body model \"{txtInputBody.Text}\" does not exist."); return(false); } } if (chkGenerateModel.Checked) { if (!CheckOutputDir(txtOutputModel.Text)) { return(false); } } if (chkOptApplyCharHeight.Checked) { if (!uint.TryParse(txtOptCharHeight.Text, out var height) || (height <= 100 || 200 <= height)) { Alert("Please enter a valid idol height."); } } if (chkGenerateCharAnim.Checked) { if (!File.Exists(txtInputDance.Text)) { Alert($"Dance data \"{txtInputDance.Text}\" does not exist."); return(false); } if (!CheckOutputDir(txtOutputCharAnim.Text)) { return(false); } } if (chkGenerateCharAnim.Checked || chkGenerateLipSync.Checked || chkGenerateFacialExpression.Checked || chkGenerateCameraMotion.Checked) { if (!File.Exists(txtInputScenario.Text)) { Alert($"Scenario data \"{txtInputScenario.Text}\" does not exist."); return(false); } } if (chkGenerateLipSync.Checked) { if (!CheckOutputDir(txtOutputLipSync.Text)) { return(false); } } if (chkGenerateFacialExpression.Checked) { if (!File.Exists(txtOptFEMappings.Text)) { Alert($"Facial expression mapping file \"{txtOptFEMappings.Text}\" does not exist."); return(false); } if (!CheckOutputDir(txtOutputFacialExpression.Text)) { return(false); } } if (chkGenerateCameraMotion.Checked) { if (!File.Exists(txtInputCamera.Text)) { Alert($"Camera data \"{txtInputCamera.Text}\" does not exist."); return(false); } if (!CheckOutputDir(txtOutputCameraMotion.Text)) { return(false); } } if (radOptCamFormatVmd.Checked) { if (!uint.TryParse(txtOptFixedFov.Text, out var u) || u == 0) { Alert($"FOV value \"{txtOptFixedFov.Text}\" should be a valid positive integer."); return(false); } } if (chkGenerateModel.Checked || chkGenerateCharAnim.Checked) { if (!Regex.IsMatch(txtInputHead.Text, @"ch_[a-z]{2}\d{3}_(?:\d{3}[a-z]{3}|[a-z])(?:_v2)?\.unity3d$", RegexOptions.CultureInvariant)) { Alert($"File \"{txtInputHead.Text}\" does not look like a character head file from the game."); return(false); } if (!Regex.IsMatch(txtInputBody.Text, @"cb_[a-z]{2}\d{3}_(?:\d{3}[a-z]{3}|[a-z])\.unity3d$", RegexOptions.CultureInvariant)) { Alert($"File \"{txtInputBody.Text}\" does not look like a character body file from the game."); return(false); } } if (chkGenerateCharAnim.Checked) { if (!Regex.IsMatch(txtInputDance.Text, @"dan_[a-z0-9]{5}[a-z0-9+]_0[12345](?:\.imo)?\.unity3d$", RegexOptions.CultureInvariant)) { Alert($"File \"{txtInputDance.Text}\" does not look like a dance data file from the game."); return(false); } } if (chkGenerateCharAnim.Checked || chkGenerateLipSync.Checked || chkGenerateFacialExpression.Checked || chkGenerateCameraMotion.Checked) { if (!Regex.IsMatch(txtInputScenario.Text, @"scrobj_[a-z0-9]{5}[a-z0-9+]\.unity3d$", RegexOptions.CultureInvariant)) { Alert($"File \"{txtInputScenario.Text}\" does not look like a scenario data file from the game."); return(false); } } if (chkGenerateCameraMotion.Checked) { if (!Regex.IsMatch(txtInputCamera.Text, @"cam_[a-z0-9]{5}[a-z0-9+](?:\.imo)?\.unity3d$", RegexOptions.CultureInvariant)) { Alert($"File \"{txtInputCamera.Text}\" does not look like a camera data file from the game."); return(false); } if (radOptCamFormatVmd.Checked) { if (!txtOutputCameraMotion.Text.EndsWith(".vmd", StringComparison.OrdinalIgnoreCase)) { if (!Confirm("The output camera file name does not ends with \".vmd\". Are you sure to continue?")) { return(false); } } } else if (radOptCamFormatMvd.Checked) { if (!txtOutputCameraMotion.Text.EndsWith(".mvd", StringComparison.OrdinalIgnoreCase)) { if (!Confirm("The output camera file name does not ends with \".mvd\". Are you sure to continue?")) { return(false); } } } } if (cboOptAppealType.SelectedIndex > 0) { if (!string.IsNullOrWhiteSpace(txtOptExternalDanceAppealFile.Text)) { if (!Regex.IsMatch(txtOptExternalDanceAppealFile.Text, @"dan_[a-z0-9]{5}[a-z0-9+]_ap\.imo\.unity3d$", RegexOptions.CultureInvariant)) { Alert($"File \"{txtOptExternalDanceAppealFile.Text}\" does not look like an external appeal data file from the game."); return(false); } } } return(true); } MainWorkerInputParams PrepareInputParams() { var ip = new MainWorkerInputParams(); ip.GenerateModel = chkGenerateModel.Checked; ip.GenerateCharacterMotion = chkGenerateCharAnim.Checked; ip.GenerateLipSync = chkGenerateLipSync.Checked; ip.GenerateFacialExpressions = chkGenerateFacialExpression.Checked; ip.GenerateCameraMotion = chkGenerateCameraMotion.Checked; ip.InputHead = txtInputHead.Text; ip.InputBody = txtInputBody.Text; ip.InputDance = txtInputDance.Text; ip.InputScenario = txtInputScenario.Text; ip.InputCamera = txtInputCamera.Text; ip.OutputModel = txtOutputModel.Text; ip.OutputCharacterAnimation = txtOutputCharAnim.Text; ip.OutputLipSync = txtOutputLipSync.Text; ip.OutputFacialExpressions = txtOutputFacialExpression.Text; ip.OutputCamera = txtOutputCameraMotion.Text; ip.MotionSource = radOptMotionSourceMltd.Checked ? MotionFormat.Mltd : MotionFormat.Mmd; ip.ScalePmx = chkOptScalePmx.Checked; ip.ConsiderIdolHeight = chkOptApplyCharHeight.Checked; var idolHeightInCm = Convert.ToUInt32(txtOptCharHeight.Text) / 100.0f; ip.IdolHeight = ip.ConsiderIdolHeight ? idolHeightInCm : 0; ip.TranslateBoneNames = chkOptTranslateBoneNames.Checked; ip.AppendLegIkBones = chkOptAppendLegIKBones.Checked; ip.FixCenterBones = chkOptFixCenterBones.Checked; ip.ConvertBindingPose = chkOptConvertToTdaPose.Checked; ip.AppendEyeBones = chkOptAppendEyeBones.Checked; ip.HideUnityGeneratedBones = chkOptHideUnityGenBones.Checked; ip.TranslateFacialExpressionNames = chkOptTranslateFacialExpressionNames.Checked; ip.ImportPhysics = chkOptImportPhysics.Checked; ip.ApplyGameStyledToon = chkGameToon.Checked; ip.SkinToonNumber = cboGameToonSkinNumber.SelectedIndex + 1; ip.ClothesToonNumber = cboGameToonClothesNumber.SelectedIndex + 1; ip.AddHairHighlights = chkOptAddHighlightHair.Checked; ip.AddEyesHighlights = chkOptAddHighlightEyes.Checked; ip.TransformTo30Fps = radOptAnimFrameRate30.Checked; ip.ScaleVmd = chkOptScaleVmd.Checked; ip.UseMvdForCamera = radOptCamFormatMvd.Checked; ip.FixedFov = ip.UseMvdForCamera ? 0 : Convert.ToUInt32(txtOptFixedFov.Text); ip.MotionNumber = cboOptMotionNumber.SelectedIndex + 1; ip.FormationNumber = cboOptFormationNumber.SelectedIndex + 1; ip.AppealType = (AppealType)cboOptAppealType.SelectedIndex; ip.ExternalDanceAppealFile = txtOptExternalDanceAppealFile.Text; ip.IgnoreSingControl = chkOptAlwaysSinging.Checked; ip.FacialExpressionMappingFilePath = txtOptFEMappings.Text; ip.PreferredFacialExpressionSource = radFESourceLandscape.Checked ? MainWorkerInputParams.FallbackFacialExpressionSource.Landscape : MainWorkerInputParams.FallbackFacialExpressionSource.Portrait; return(ip); } txtLog.Clear(); MainWorkerInputParams p; try { if (!CheckInputParams()) { return; } p = PrepareInputParams(); } catch (Exception ex) { Alert(ex.Message); return; } var worker = new MainWorker(this, p); worker.Start(); EnableMainControls(false); }
private void DoWork(object state) { ConversionConfig PrepareConversionConfig(InputParams ip) { var cc = new ConversionConfig(); cc.MotionFormat = ip.MotionSource; cc.ScaleToPmxSize = ip.ScalePmx; cc.ApplyPmxCharacterHeight = ip.ConsiderIdolHeight; cc.TranslateBoneNamesToMmd = ip.TranslateBoneNames; cc.AppendIKBones = ip.AppendLegIkBones; cc.FixMmdCenterBones = ip.FixCenterBones; cc.FixTdaBindingPose = ip.ConvertBindingPose; cc.AppendEyeBones = ip.AppendEyeBones; cc.HideUnityGeneratedBones = ip.HideUnityGeneratedBones; cc.SkeletonFormat = ip.MotionSource == MotionFormat.Mltd ? SkeletonFormat.Mltd : SkeletonFormat.Mmd; cc.TranslateFacialExpressionNamesToMmd = ip.TranslateFacialExpressionNames; cc.ImportPhysics = ip.ImportPhysics; cc.Transform60FpsTo30Fps = ip.TransformTo30Fps; cc.ScaleToVmdSize = ip.ScaleVmd; { var mappingsJson = File.ReadAllText(ip.FacialExpressionMappingFilePath, Encoding.UTF8); var mappingsObj = JsonConvert.DeserializeObject <FacialConfig>(mappingsJson); var dict = new Dictionary <int, IReadOnlyDictionary <string, float> >(); foreach (var expr in mappingsObj.Expressions) { var d2 = new Dictionary <string, float>(); foreach (var kv in expr.Data) { d2[kv.Key] = kv.Value; } dict[expr.Key] = d2; } cc.FacialExpressionMappings = dict; } return(cc); } Debug.Assert(InvokeRequired, "The worker procedure should be running on the worker thread."); Log("Worker started."); try { var p = (InputParams)state; ConversionConfig.Current = PrepareConversionConfig(p); if (p.IdolHeight <= 0) { throw new ArgumentOutOfRangeException(nameof(p.IdolHeight), "Invalid idol height."); } ScalingConfig.CharacterHeight = p.IdolHeight; do { var bodyAvatar = ResourceLoader.LoadBodyAvatar(p.InputBody); if (bodyAvatar == null) { Log("Failed to load body avatar."); break; } var bodyMesh = ResourceLoader.LoadBodyMesh(p.InputBody); if (bodyMesh == null) { Log("Failed to load body mesh."); break; } var headAvatar = ResourceLoader.LoadHeadAvatar(p.InputHead); if (headAvatar == null) { Log("Failed to load head avatar."); break; } var headMesh = ResourceLoader.LoadHeadMesh(p.InputHead); if (headMesh == null) { Log("Failed to load head mesh."); break; } var(bodySway, headSway) = ResourceLoader.LoadSwayControllers(p.InputBody, p.InputHead); if (bodySway == null || headSway == null) { Log("Failed to load sway controllers."); break; } var combinedAvatar = CompositeAvatar.FromAvatars(bodyAvatar, headAvatar); var combinedMesh = CompositeMesh.FromMeshes(bodyMesh, headMesh); CharacterImasMotionAsset dance; ScenarioObject scenario; if (p.GenerateCharacterMotion) { (dance, _, _) = ResourceLoader.LoadDance(p.InputDance, p.SongPosition); if (dance == null) { Log("Failed to load dance data."); break; } scenario = ResourceLoader.LoadScenario(p.InputFacialExpression); if (scenario == null) { Log("Failed to load scenario object."); break; } } else { dance = null; scenario = null; } CharacterImasMotionAsset camera; if (p.GenerateCameraMotion) { camera = ResourceLoader.LoadCamera(p.InputCamera); if (camera == null) { Log("Failed to load camera data."); break; } } else { camera = null; } do { // Now file names are like "ch_pr001_201xxx.unity3d". var avatarName = (new FileInfo(p.InputHead).Name).Substring(3, 12); // ss001_015siz -> 015ss001 // Note: PMD allows max 19 characters in texture file names. // In the format below, textures will be named like: // tex\015ss001_01.png // which is at the limit. var texPrefix = avatarName.Substring(6, 3) + avatarName.Substring(0, 5); texPrefix = @"tex\" + texPrefix + "_"; Log("Generating model..."); var pmxCreator = new PmxCreator(); var pmx = pmxCreator.CreateFrom(combinedAvatar, combinedMesh, bodyMesh.VertexCount, texPrefix, bodySway, headSway); if (p.GenerateModel) { Log("Saving model..."); using (var w = new PmxWriter(File.Open(p.OutputModel, FileMode.Create, FileAccess.Write, FileShare.Write))) { w.Write(pmx); } } if (p.GenerateCharacterMotion) { Log("Generating character motion..."); var creator = new VmdCreator { ProcessBoneFrames = true, ProcessCameraFrames = false, ProcessFacialFrames = true, ProcessLightFrames = false }; var danceVmd = creator.CreateFrom(dance, combinedAvatar, pmx, null, scenario, p.SongPosition); Log("Saving character motion..."); using (var w = new VmdWriter(File.Open(p.OutputCharacterAnimation, FileMode.Create, FileAccess.Write, FileShare.Write))) { w.Write(danceVmd); } } if (p.GenerateCameraMotion) { Log("Generating camera motion..."); if (p.UseMvd) { var creator = new MvdCreator { ProcessBoneFrames = false, ProcessCameraFrames = true, ProcessFacialFrames = false, ProcessLightFrames = false }; var motion = creator.CreateFrom(null, null, null, camera, null, p.SongPosition); Log("Writing camera motion..."); using (var w = new MvdWriter(File.Open(p.OutputCamera, FileMode.Create, FileAccess.Write, FileShare.Write))) { w.Write(motion); } } else { var creator = new VmdCreator { ProcessBoneFrames = false, ProcessCameraFrames = true, ProcessFacialFrames = false, ProcessLightFrames = false }; var motion = creator.CreateFrom(null, null, null, camera, null, p.SongPosition); Log("Writing camera motion..."); using (var w = new VmdWriter(File.Open(p.OutputCamera, FileMode.Create, FileAccess.Write, FileShare.Write))) { w.Write(motion); } } } } while (false); } while (false); } catch (Exception ex) { MessageBox.Show(ex.ToString(), ApplicationHelper.GetApplicationTitle(), MessageBoxButtons.OK, MessageBoxIcon.Warning); } Log("Done."); Invoke(() => EnableMainControls(true)); }