1
+ using System ;
2
+ using System . Collections ;
3
+ using System . Collections . Generic ;
4
+ using System . IO ;
5
+ using System . Linq ;
6
+ using System . Net . Http ;
7
+ using System . Text ;
8
+ using GluonGui . Dialog ;
9
+ using MCPServer . Editor . Commands ;
10
+ using Newtonsoft . Json . Linq ;
11
+ using UnityEditor ;
12
+ using UnityEngine ;
13
+ using UnityEngine . Rendering ;
14
+ using UnityEngine . Rendering . Universal ;
15
+
16
+ #if UNITY_EDITOR
17
+ namespace Hyper3DRodin {
18
+ public static class Hyper3DRodinCommandHandler
19
+ {
20
+ public static object GetHyper3DStatus ( )
21
+ {
22
+ // Access settings globally
23
+ bool enabled = SettingsManager . enabled ;
24
+ string apiKey = SettingsManager . apiKey ;
25
+ ServiceProvider mode = SettingsManager . serviceProvider ;
26
+
27
+ if ( enabled )
28
+ {
29
+ if ( string . IsNullOrEmpty ( apiKey ) )
30
+ {
31
+ return new
32
+ {
33
+ enabled = false ,
34
+ message = @"Hyper3D Rodin integration is currently enabled, but no API key is provided. To enable it:
35
+ 1. Open Unity's menu and go to **Window > Unity MCP > Hyper3D Rodin**.
36
+ 2. Ensure the **'Enable Hyper3D Rodin Service'** checkbox is checked.
37
+ 3. Select the appropriate **service provider**.
38
+ 4. Enter your **API Key** in the provided input field.
39
+ 5. Restart the connection for changes to take effect."
40
+ } ;
41
+ }
42
+
43
+ string keyType = apiKey == Constants . GetFreeTrialKey ( ) ? "free_trial" : "private" ;
44
+ string message = $ "Hyper3D Rodin integration is enabled and ready to use. Provider: { mode } . " +
45
+ $ "Key type: { keyType } ";
46
+
47
+ return new
48
+ {
49
+ enabled = true ,
50
+ message = message
51
+ } ;
52
+ }
53
+ else
54
+ {
55
+ return new
56
+ {
57
+ enabled = false ,
58
+ message = @"Hyper3D Rodin integration is currently disabled. To enable it:
59
+ 1. Open Unity's menu and go to **Window > Unity MCP > Hyper3D Rodin**.
60
+ 2. Check the **'Enable Hyper3D Rodin Service'** option.
61
+ 3. Restart the connection for changes to take effect."
62
+ } ;
63
+ }
64
+ }
65
+
66
+ public static object CreateRodinJob ( JObject @params )
67
+ {
68
+ switch ( SettingsManager . serviceProvider )
69
+ {
70
+ case ServiceProvider . MAIN_SITE :
71
+ return CreateRodinJobMainSite ( @params ) ;
72
+ case ServiceProvider . FAL_AI :
73
+ return CreateRodinJobFalAi ( @params ) ;
74
+ default :
75
+ return new { error = "Error: Unknown Hyper3D Rodin mode!" } ;
76
+ }
77
+ }
78
+
79
+ private static object CreateRodinJobMainSite ( JObject @params )
80
+ {
81
+ HttpClient client = new HttpClient ( ) ;
82
+ try
83
+ {
84
+ var formData = new MultipartFormDataContent ( ) ;
85
+ if ( @params [ "images" ] is JArray imagesArray )
86
+ {
87
+ int i = 0 ;
88
+ foreach ( var img in imagesArray )
89
+ {
90
+ string imgSuffix = img [ "suffix" ] ? . ToString ( ) ;
91
+ string imgPath = img [ "path" ] ? . ToString ( ) ;
92
+ if ( ! string . IsNullOrEmpty ( imgPath ) && File . Exists ( imgPath ) )
93
+ {
94
+ formData . Add ( new ByteArrayContent ( File . ReadAllBytes ( imgPath ) ) , "images" , $ "{ i : D4} { imgSuffix } ") ;
95
+ i ++ ;
96
+ }
97
+ }
98
+ }
99
+
100
+ formData . Add ( new StringContent ( "Sketch" ) , "tier" ) ;
101
+ formData . Add ( new StringContent ( "Raw" ) , "mesh_mode" ) ;
102
+
103
+
104
+ if ( @params [ "text_prompt" ] != null )
105
+ formData . Add ( new StringContent ( @params [ "text_prompt" ] . ToString ( ) ) , "prompt" ) ;
106
+
107
+ if ( @params [ "bbox_condition" ] != null )
108
+ formData . Add ( new StringContent ( @params [ "bbox_condition" ] . ToString ( ) ) , "bbox_condition" ) ;
109
+
110
+ var request = new HttpRequestMessage ( HttpMethod . Post , "https://hyperhuman.deemos.com/api/v2/rodin" )
111
+ {
112
+ Headers = { { "Authorization" , $ "Bearer { SettingsManager . apiKey } " } } ,
113
+ Content = formData
114
+ } ;
115
+
116
+ HttpResponseMessage response = client . SendAsync ( request ) . Result ;
117
+ string responseBody = response . Content . ReadAsStringAsync ( ) . Result ;
118
+ return JObject . Parse ( responseBody ) ;
119
+ }
120
+ catch ( Exception e )
121
+ {
122
+ return new JObject { [ "error" ] = e . Message } ;
123
+ }
124
+ }
125
+
126
+ private static object CreateRodinJobFalAi ( JObject @params )
127
+ {
128
+ HttpClient client = new HttpClient ( ) ;
129
+ try
130
+ {
131
+ var requestData = new JObject
132
+ {
133
+ [ "tier" ] = "Sketch" ,
134
+ } ;
135
+
136
+ if ( @params [ "images" ] is JArray imagesArray )
137
+ requestData [ "input_image_urls" ] = imagesArray ;
138
+
139
+ if ( @params [ "text_prompt" ] != null )
140
+ requestData [ "prompt" ] = @params [ "text_prompt" ] . ToString ( ) ;
141
+
142
+ if ( @params [ "bbox_condition" ] != null )
143
+ requestData [ "bbox_condition" ] = @params [ "bbox_condition" ] ;
144
+
145
+ var request = new HttpRequestMessage ( HttpMethod . Post , "https://queue.fal.run/fal-ai/hyper3d/rodin" )
146
+ {
147
+ Headers = { { "Authorization" , $ "Key { SettingsManager . apiKey } " } } ,
148
+ Content = new StringContent ( requestData . ToString ( ) , Encoding . UTF8 , "application/json" )
149
+ } ;
150
+
151
+ HttpResponseMessage response = client . SendAsync ( request ) . Result ;
152
+ string responseBody = response . Content . ReadAsStringAsync ( ) . Result ;
153
+ return JObject . Parse ( responseBody ) ;
154
+ }
155
+ catch ( Exception e )
156
+ {
157
+ return new JObject { [ "error" ] = e . Message } ;
158
+ }
159
+ }
160
+
161
+ public static object PollRodinJobStatus ( JObject @params )
162
+ {
163
+ switch ( SettingsManager . serviceProvider )
164
+ {
165
+ case ServiceProvider . MAIN_SITE :
166
+ return PollRodinJobStatusMainSite ( @params ) ;
167
+ case ServiceProvider . FAL_AI :
168
+ return PollRodinJobStatusFalAi ( @params ) ;
169
+ default :
170
+ return new JObject { [ "error" ] = "Error: Unknown Hyper3D Rodin mode!" } ;
171
+ }
172
+ }
173
+
174
+ private static object PollRodinJobStatusMainSite ( JObject @params )
175
+ {
176
+ HttpClient client = new HttpClient ( ) ;
177
+ try
178
+ {
179
+ var requestData = new JObject
180
+ {
181
+ [ "subscription_key" ] = @params [ "subscription_key" ]
182
+ } ;
183
+
184
+ var request = new HttpRequestMessage ( HttpMethod . Post , "https://hyperhuman.deemos.com/api/v2/status" )
185
+ {
186
+ Headers = { { "Authorization" , $ "Bearer { SettingsManager . apiKey } " } } ,
187
+ Content = new StringContent ( requestData . ToString ( ) , Encoding . UTF8 , "application/json" ) ,
188
+ } ;
189
+
190
+ HttpResponseMessage response = client . SendAsync ( request ) . Result ;
191
+ string responseBody = response . Content . ReadAsStringAsync ( ) . Result ;
192
+ return JObject . Parse ( responseBody ) ;
193
+ }
194
+ catch ( Exception e )
195
+ {
196
+ return new JObject { [ "error" ] = e . Message } ;
197
+ }
198
+ }
199
+
200
+ private static object PollRodinJobStatusFalAi ( JObject @params )
201
+ {
202
+ HttpClient client = new HttpClient ( ) ;
203
+ try
204
+ {
205
+ string requestId = @params [ "request_id" ] ? . ToString ( ) ;
206
+ if ( string . IsNullOrEmpty ( requestId ) )
207
+ return new JObject { [ "error" ] = "Invalid request ID" } ;
208
+
209
+ var request = new HttpRequestMessage ( HttpMethod . Get , $ "https://queue.fal.run/fal-ai/hyper3d/requests/{ requestId } /status")
210
+ {
211
+ Headers = { { "Authorization" , $ "Key { SettingsManager . apiKey } " } }
212
+ } ;
213
+
214
+ HttpResponseMessage response = client . SendAsync ( request ) . Result ;
215
+ string responseBody = response . Content . ReadAsStringAsync ( ) . Result ;
216
+ return JObject . Parse ( responseBody ) ;
217
+ }
218
+ catch ( Exception e )
219
+ {
220
+ return new JObject { [ "error" ] = e . Message } ;
221
+ }
222
+ }
223
+
224
+ public static object DownloadRodinJobResult ( JObject @params )
225
+ {
226
+ switch ( SettingsManager . serviceProvider )
227
+ {
228
+ case ServiceProvider . MAIN_SITE :
229
+ return DownloadRodinJobResultMainSite ( @params ) ;
230
+ case ServiceProvider . FAL_AI :
231
+ return DownloadRodinJobResultFalAi ( @params ) ;
232
+ default :
233
+ return new JObject { [ "error" ] = "Error: Unknown Hyper3D Rodin mode!" } ;
234
+ }
235
+ }
236
+
237
+ private static object DownloadRodinJobResultMainSite ( JObject @params )
238
+ {
239
+ HttpClient client = new HttpClient ( ) ;
240
+ try
241
+ {
242
+ // Extract parameters
243
+ string taskUuid = @params [ "task_uuid" ] ? . ToString ( ) ;
244
+ string savePath = @params [ "path" ] ? . ToString ( ) ;
245
+
246
+ if ( string . IsNullOrEmpty ( taskUuid ) || string . IsNullOrEmpty ( savePath ) )
247
+ return new JObject { [ "error" ] = "Missing required parameters: task_uuid or path" } ;
248
+
249
+ // Prepare API request
250
+ var request = new HttpRequestMessage ( HttpMethod . Post , "https://hyperhuman.deemos.com/api/v2/download" )
251
+ {
252
+ Headers = { { "Authorization" , $ "Bearer { SettingsManager . apiKey } " } } ,
253
+ Content = new StringContent ( new JObject { [ "task_uuid" ] = taskUuid } . ToString ( ) , Encoding . UTF8 , "application/json" )
254
+ } ;
255
+
256
+ // Send request
257
+ HttpResponseMessage response = client . SendAsync ( request ) . Result ;
258
+ string responseBody = response . Content . ReadAsStringAsync ( ) . Result ;
259
+ JObject data = JObject . Parse ( responseBody ) ;
260
+
261
+ // Find GLB file URL
262
+ foreach ( var item in data [ "list" ] )
263
+ {
264
+ if ( item [ "name" ] . ToString ( ) . EndsWith ( ".glb" ) )
265
+ {
266
+ JObject @result = JObject . FromObject (
267
+ DownloadFile ( item [ "url" ] . ToString ( ) , savePath + "/" + item [ "name" ] )
268
+ ) ;
269
+ if ( @result [ "error" ] != null ) {
270
+ return result ;
271
+ }
272
+ }
273
+ }
274
+
275
+ return new JObject { [ "succeed" ] = true } ;
276
+ }
277
+ catch ( Exception e )
278
+ {
279
+ return new JObject { [ "error" ] = e . Message } ;
280
+ }
281
+ }
282
+
283
+ private static object DownloadRodinJobResultFalAi ( JObject @params )
284
+ {
285
+ HttpClient client = new HttpClient ( ) ;
286
+ try
287
+ {
288
+ // Extract parameters
289
+ string requestId = @params [ "request_id" ] ? . ToString ( ) ;
290
+ string savePath = @params [ "path" ] ? . ToString ( ) ;
291
+
292
+ if ( string . IsNullOrEmpty ( requestId ) || string . IsNullOrEmpty ( savePath ) )
293
+ return new JObject { [ "error" ] = "Missing required parameters: request_id or path" } ;
294
+
295
+ // Prepare API request
296
+ var request = new HttpRequestMessage ( HttpMethod . Get , $ "https://queue.fal.run/fal-ai/hyper3d/requests/{ requestId } ")
297
+ {
298
+ Headers = { { "Authorization" , $ "Key { SettingsManager . apiKey } " } }
299
+ } ;
300
+
301
+ // Send request
302
+ HttpResponseMessage response = client . SendAsync ( request ) . Result ;
303
+ string responseBody = response . Content . ReadAsStringAsync ( ) . Result ;
304
+ JObject data = JObject . Parse ( responseBody ) ;
305
+
306
+ // Find GLB file URL
307
+ string fileUrl = data [ "model_mesh" ] ? [ "url" ] ? . ToString ( ) ;
308
+ if ( string . IsNullOrEmpty ( fileUrl ) )
309
+ return new JObject { [ "error" ] = "No .glb file found in response" } ;
310
+
311
+ return DownloadFile ( fileUrl , savePath ) ;
312
+ }
313
+ catch ( Exception e )
314
+ {
315
+ return new JObject { [ "error" ] = e . Message } ;
316
+ }
317
+ }
318
+
319
+ private static object DownloadFile ( string fileUrl , string filePath )
320
+ {
321
+ HttpClient client = new HttpClient ( ) ;
322
+ try
323
+ {
324
+ // Ensure filePath starts with "Assets/"
325
+ if ( ! filePath . StartsWith ( "Assets/" ) )
326
+ return new JObject { [ "error" ] = "Invalid file path. Path must start with 'Assets/'" } ;
327
+
328
+ // Convert Unity-relative path to absolute system path
329
+ string absolutePath = Path . Combine ( Application . dataPath , filePath . Substring ( 7 ) ) ; // Remove "Assets/" prefix
330
+
331
+ // Ensure directory exists
332
+ string directory = Path . GetDirectoryName ( absolutePath ) ;
333
+ if ( ! Directory . Exists ( directory ) )
334
+ Directory . CreateDirectory ( directory ) ;
335
+
336
+ // Prepare download request
337
+ var request = new HttpRequestMessage ( HttpMethod . Get , fileUrl ) ;
338
+ HttpResponseMessage response = client . SendAsync ( request ) . Result ;
339
+
340
+ if ( ! response . IsSuccessStatusCode )
341
+ return new JObject { [ "error" ] = $ "Failed to download file. HTTP Status: { response . StatusCode } " } ;
342
+
343
+ // Save file to path
344
+ using ( var fs = new FileStream ( absolutePath , FileMode . Create , FileAccess . Write ) )
345
+ {
346
+ response . Content . CopyToAsync ( fs ) . Wait ( ) ;
347
+ }
348
+
349
+ // Return Unity-relative path
350
+ return new JObject { [ "succeed" ] = true , [ "path" ] = filePath } ;
351
+ }
352
+ catch ( Exception e )
353
+ {
354
+ return new JObject { [ "error" ] = e . Message } ;
355
+ }
356
+ }
357
+ }
358
+ }
359
+ #endif
0 commit comments