lithop 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/Cargo.lock +284 -0
  4. data/Cargo.toml +6 -0
  5. data/README.md +34 -13
  6. data/RELEASING.md +24 -6
  7. data/docs/README.md +6 -3
  8. data/docs/artifacts.md +35 -16
  9. data/docs/bambu-studio.md +17 -11
  10. data/docs/calibration.md +4 -4
  11. data/docs/color-model.md +20 -18
  12. data/docs/command.md +53 -14
  13. data/docs/config.md +43 -31
  14. data/docs/getting-started.md +17 -8
  15. data/docs/images/cmyw-lithophane-preview.png +0 -0
  16. data/docs/local-ui.md +117 -0
  17. data/docs/tiling.md +20 -19
  18. data/docs/troubleshooting.md +12 -2
  19. data/ext/lithop_native/Cargo.toml +17 -0
  20. data/ext/lithop_native/build.rs +4 -0
  21. data/ext/lithop_native/extconf.rb +6 -0
  22. data/ext/lithop_native/src/column_mesh.rs +388 -0
  23. data/ext/lithop_native/src/lib.rs +37 -0
  24. data/ext/lithop_native/src/mesh.rs +187 -0
  25. data/ext/lithop_native/src/palette.rs +67 -0
  26. data/ext/lithop_native/src/xml.rs +134 -0
  27. data/lib/lithop/calibration/export.rb +2 -2
  28. data/lib/lithop/calibration/target.rb +5 -5
  29. data/lib/lithop/cli/image_adjustment_options.rb +33 -0
  30. data/lib/lithop/cli/image_options.rb +5 -18
  31. data/lib/lithop/cli/options.rb +17 -4
  32. data/lib/lithop/cli.rb +28 -0
  33. data/lib/lithop/export/color_metadata.rb +46 -23
  34. data/lib/lithop/export/generation.rb +38 -27
  35. data/lib/lithop/export/job.rb +2 -2
  36. data/lib/lithop/export/metadata.rb +8 -3
  37. data/lib/lithop/export/planning.rb +82 -45
  38. data/lib/lithop/export/previews.rb +25 -1
  39. data/lib/lithop/export/print_readme.rb +20 -4
  40. data/lib/lithop/formats/three_mf/bambu_metadata.rb +11 -8
  41. data/lib/lithop/formats/three_mf/mesh.rb +72 -3
  42. data/lib/lithop/formats/three_mf/model_writer.rb +21 -21
  43. data/lib/lithop/formats/three_mf/part_writer.rb +37 -0
  44. data/lib/lithop/formats/three_mf.rb +1 -1
  45. data/lib/lithop/image/pipeline.rb +84 -31
  46. data/lib/lithop/image/svg_loader.rb +16 -7
  47. data/lib/lithop/job_execution.rb +132 -4
  48. data/lib/lithop/layer_structure.rb +0 -9
  49. data/lib/lithop/mesh/column_geometry.rb +8 -0
  50. data/lib/lithop/mesh/column_topology.rb +2 -0
  51. data/lib/lithop/model/filament_profile.rb +3 -3
  52. data/lib/lithop/model/material_display_map.rb +2 -1
  53. data/lib/lithop/model/material_model.rb +17 -5
  54. data/lib/lithop/model/palette.rb +21 -3
  55. data/lib/lithop/model/z_region_model.rb +44 -0
  56. data/lib/lithop/native.rb +39 -0
  57. data/lib/lithop/settings.rb +147 -15
  58. data/lib/lithop/settings_file.rb +1 -0
  59. data/lib/lithop/tiling.rb +3 -2
  60. data/lib/lithop/version.rb +1 -1
  61. data/lib/lithop/web/app.rb +517 -0
  62. data/lib/lithop/web/bambu_studio_launcher.rb +82 -0
  63. data/lib/lithop/web/job_launcher.rb +214 -0
  64. data/lib/lithop/web/job_store.rb +390 -0
  65. data/lib/lithop/web/preview_store.rb +182 -0
  66. data/lib/lithop/web/public/app.css +330 -0
  67. data/lib/lithop/web/public/app.js +1949 -0
  68. data/lib/lithop/web/server.rb +21 -0
  69. data/lib/lithop/web/settings_form.rb +67 -0
  70. data/lib/lithop/web/settings_library.rb +154 -0
  71. data/lib/lithop/web/views/index.erb +418 -0
  72. data/lib/lithop/web/worker.rb +27 -0
  73. data/lib/lithop.rb +3 -1
  74. data/sig/lithop.rbs +41 -0
  75. metadata +86 -3
  76. data/lib/lithop/model/layer_region_model.rb +0 -24
@@ -0,0 +1,418 @@
1
+ <!doctype html>
2
+ <html lang="ja">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="csrf-token" content="<%= h @csrf_token %>">
7
+ <title>Lithop Local UI</title>
8
+ <link rel="stylesheet" href="/app.css">
9
+ <script src="/app.js" defer></script>
10
+ </head>
11
+ <body>
12
+ <header class="site-header">
13
+ <div class="site-header-main">
14
+ <div class="brand-block">
15
+ <p class="eyebrow">LOCAL CMYW WORKSPACE</p>
16
+ <a class="brand" href="/?view=create" data-view-target="create">Lithop</a>
17
+ </div>
18
+ <div class="header-context">
19
+ <span class="project-name"><%= h @project_name %></span>
20
+ <span aria-hidden="true">/</span>
21
+ <strong id="current-view-label"><%= { "create" => "作成", "jobs" => "ジョブ", "presets" => "プリセット" }.fetch(@initial_view) %></strong>
22
+ </div>
23
+ <div id="global-job-control" class="global-job-control" <%= "hidden" unless @selected_job_id %>>
24
+ <button id="global-job-link" class="header-action" type="button"><span id="global-job-label">ジョブを取得中</span><small id="global-job-percent">0%</small></button>
25
+ <button id="global-cancel-button" class="header-action danger-text" type="button">停止</button>
26
+ </div>
27
+ <button id="help-button" class="header-action" type="button">使い方</button>
28
+ <span class="local-badge">127.0.0.1</span>
29
+ </div>
30
+ <nav id="primary-navigation" class="primary-navigation" aria-label="主メニュー">
31
+ <button type="button" data-view-target="create" <%= 'aria-current="page"' if @initial_view == "create" %>><span>01</span><strong>作成</strong><small>画像と設定</small></button>
32
+ <button type="button" data-view-target="jobs" <%= 'aria-current="page"' if @initial_view == "jobs" %>><span>02</span><strong>ジョブ</strong><small>履歴と成果物</small></button>
33
+ <button type="button" data-view-target="presets" <%= 'aria-current="page"' if @initial_view == "presets" %>><span>03</span><strong>プリセット</strong><small>設定の再利用</small></button>
34
+ </nav>
35
+ </header>
36
+
37
+ <main class="workspace" data-selected-job="<%= h @selected_job_id %>" data-view="<%= h @initial_view %>">
38
+ <div id="page-notice" class="notice error" role="alert" <%= "hidden" unless @error %>><%= h @error %></div>
39
+
40
+ <div class="app-shell">
41
+ <div class="app-content">
42
+
43
+ <section id="view-create" class="app-view" data-app-view="create" aria-labelledby="create-view-title" <%= "hidden" unless @initial_view == "create" %>>
44
+ <h1 id="create-view-title" class="view-title" tabindex="-1">作成</h1>
45
+ <div class="workspace-primary">
46
+ <section class="panel editor-panel">
47
+ <div class="section-heading">
48
+ <div><p class="step">CREATE</p><h2>画像と設定</h2></div>
49
+ <span class="context-badge">編集中</span>
50
+ </div>
51
+
52
+ <form id="conversion-form" action="/jobs" method="post" enctype="multipart/form-data">
53
+ <input type="hidden" name="csrf_token" value="<%= h @csrf_token %>">
54
+ <input id="preview-id" type="hidden" name="preview_id" value="">
55
+
56
+ <label id="file-picker" class="file-picker" for="image-input" tabindex="0">
57
+ <span class="file-title">画像を選択またはドロップ</span>
58
+ <span id="file-name">ファイルをここへ選択</span>
59
+ <input id="image-input" name="image" type="file" accept=".jpeg,.jpg,.png,.svg,image/jpeg,image/png,image/svg+xml" required>
60
+ </label>
61
+
62
+ <section class="conversion-actions" aria-label="変換の開始">
63
+ <div class="conversion-action-row">
64
+ <span id="draft-state" tabindex="-1" aria-live="polite">画像を選択してください</span>
65
+ <button id="submit-button" class="primary-button" type="submit">変換を開始</button>
66
+ </div>
67
+ <details id="draft-summary" class="draft-summary">
68
+ <summary class="summary-heading"><strong id="draft-summary-title">変換する内容</strong><span id="draft-summary-change">未実行</span></summary>
69
+ <dl id="draft-summary-values"></dl>
70
+ </details>
71
+ <p id="form-error" class="notice error" role="alert" hidden></p>
72
+ </section>
73
+
74
+ <nav class="settings-navigation" aria-label="設定区分">
75
+ <button type="button" data-settings-target="settings-basic" aria-current="true">基本設定</button>
76
+ <button type="button" data-settings-target="settings-placement">画像配置</button>
77
+ <button type="button" data-settings-target="settings-adjustment">画像調整</button>
78
+ <button type="button" data-settings-target="settings-yellow">Yellow補正</button>
79
+ <button type="button" data-settings-target="settings-split">分割</button>
80
+ <button type="button" data-settings-target="settings-advanced">詳細設定</button>
81
+ </nav>
82
+
83
+ <details id="settings-basic" class="settings-section" open>
84
+ <summary><span>基本設定</span><small>寸法・セル・積層順</small></summary>
85
+ <div class="settings-body">
86
+ <div class="inline-actions">
87
+ <label class="check-label"><input id="aspect-lock" type="checkbox" checked>縦横比を固定</label>
88
+ <button id="swap-dimensions" class="text-button" type="button">幅と高さを交換</button>
89
+ <button class="text-button section-reset" type="button">初期値へ戻す</button>
90
+ </div>
91
+ <div class="form-grid">
92
+ <label>幅 <span>mm</span><input id="width" name="width" type="number" min="0.01" step="0.01" value="<%= h @values['width'] %>" data-default="<%= h @values['width'] %>" required></label>
93
+ <label>高さ <span>mm</span><input id="height" name="height" type="number" min="0.01" step="0.01" value="<%= h @values['height'] %>" data-default="<%= h @values['height'] %>" required></label>
94
+ <label>XYセル <span>mm</span><input name="pixel_size" type="number" min="0.01" step="0.01" value="<%= h @values['pixel_size'] %>" data-default="<%= h @values['pixel_size'] %>" required></label>
95
+ <label>色セル <span>mm</span><input name="color_pixel_size" type="number" min="0.01" step="0.01" value="<%= h @values['color_pixel_size'] %>" data-default="<%= h @values['color_pixel_size'] %>" placeholder="XYセルと同じ"></label>
96
+ <label>色の積層順
97
+ <select id="pigment-order" name="pigment_order" data-default="<%= h @values['pigment_order'] %>">
98
+ <% Lithop::Settings::PIGMENT_ORDERS.each do |order| %>
99
+ <% value = order.join(",") %>
100
+ <% abbreviation = order.map { |pigment| pigment.to_s[0].upcase }.join %>
101
+ <% names = order.map { |pigment| pigment.to_s.capitalize }.join(" → ") %>
102
+ <option value="<%= h value %>" <%= "selected" if selected?(:pigment_order, value) %>><%= h abbreviation %>(<%= h names %>)</option>
103
+ <% end %>
104
+ </select>
105
+ </label>
106
+ </div>
107
+ <p class="fine-print">バックライト側から観察側へ積み重ねるCyan・Magenta・Yellowの順序です。Whiteは常に最後です。</p>
108
+ </div>
109
+ </details>
110
+
111
+ <details id="settings-placement" class="settings-section" open>
112
+ <summary><span>画像配置</span><small>収め方とクロップ</small></summary>
113
+ <div class="settings-body">
114
+ <div class="section-actions"><button class="text-button section-reset" type="button">初期値へ戻す</button></div>
115
+ <div class="form-grid">
116
+ <label>配置
117
+ <select name="image_fit" id="image-fit" data-default="<%= h @values['image_fit'] %>">
118
+ <option value="cover" <%= "selected" if selected?(:image_fit, "cover") %>>全面に配置</option>
119
+ <option value="contain" <%= "selected" if selected?(:image_fit, "contain") %>>全体を収める</option>
120
+ </select>
121
+ </label>
122
+ <label class="range-field">横位置 <span>0–1</span>
123
+ <input class="range-control" type="range" min="0" max="1" step="0.01" data-sync="crop-x" aria-label="横位置スライダー">
124
+ <input id="crop-x" name="crop_position_x" type="number" min="0" max="1" step="0.01" value="<%= h @values['crop_position_x'] %>" data-default="<%= h @values['crop_position_x'] %>" required>
125
+ </label>
126
+ <label class="range-field">縦位置 <span>0–1</span>
127
+ <input class="range-control" type="range" min="0" max="1" step="0.01" data-sync="crop-y" aria-label="縦位置スライダー">
128
+ <input id="crop-y" name="crop_position_y" type="number" min="0" max="1" step="0.01" value="<%= h @values['crop_position_y'] %>" data-default="<%= h @values['crop_position_y'] %>" required>
129
+ </label>
130
+ <label class="range-field">クロップ倍率 <span>0–1</span>
131
+ <input class="range-control" type="range" min="0.01" max="1" step="0.01" data-sync="crop-scale" aria-label="クロップ倍率スライダー">
132
+ <input id="crop-scale" name="crop_scale" type="number" min="0.01" max="1" step="0.01" value="<%= h @values['crop_scale'] %>" data-default="<%= h @values['crop_scale'] %>" required>
133
+ </label>
134
+ </div>
135
+ </div>
136
+ </details>
137
+
138
+ <details id="settings-adjustment" class="settings-section" open>
139
+ <summary><span>画像調整</span><small>明るさ・コントラスト・彩度・淡色</small></summary>
140
+ <div class="settings-body">
141
+ <div class="section-actions"><button class="text-button section-reset" type="button">初期値へ戻す</button></div>
142
+ <div class="adjustment-grid">
143
+ <% { "brightness" => "明るさ", "contrast" => "コントラスト", "saturation" => "彩度" }.each do |key, label| %>
144
+ <label class="range-field"><%= label %> <span>%</span>
145
+ <input class="range-control" type="range" min="-100" max="100" step="1" data-sync="<%= key %>" aria-label="<%= label %>スライダー">
146
+ <input id="<%= key %>" name="<%= key %>" type="number" min="-100" max="100" step="1" value="<%= h @values[key] %>" data-default="<%= h @values[key] %>" required>
147
+ </label>
148
+ <% end %>
149
+ </div>
150
+ <label class="check-label"><input id="suppress-pale-colors" name="suppress_pale_colors" type="checkbox" value="1" data-default="<%= checked?(:suppress_pale_colors) ? '1' : '0' %>" <%= "checked" if checked?(:suppress_pale_colors) %>>ごく薄い色差をWhiteにする</label>
151
+ </div>
152
+ </details>
153
+
154
+ <details id="settings-yellow" class="settings-section">
155
+ <summary><span>Yellow補正</span><small>混ぜる色と割合</small></summary>
156
+ <div class="settings-body">
157
+ <div class="section-actions"><button class="text-button section-reset" type="button">初期値へ戻す</button></div>
158
+ <div class="form-grid">
159
+ <label>Yellowへ混ぜる色
160
+ <select id="yellow-mix-color" name="yellow_mix_color" data-default="<%= h @values['yellow_mix_color'] %>">
161
+ <option value="cyan" <%= "selected" if selected?(:yellow_mix_color, "cyan") %>>Cyan</option>
162
+ <option value="magenta" <%= "selected" if selected?(:yellow_mix_color, "magenta") %>>Magenta</option>
163
+ </select>
164
+ </label>
165
+ <label class="range-field">Yellow混色率 <span>%</span>
166
+ <input class="range-control" type="range" min="0" max="100" step="1" data-sync="yellow-mix-percent" aria-label="Yellow混色率スライダー">
167
+ <input id="yellow-mix-percent" name="yellow_mix_percent" type="number" min="0" max="100" step="1" value="<%= h @values['yellow_mix_percent'] %>" data-default="<%= h @values['yellow_mix_percent'] %>" required>
168
+ </label>
169
+ </div>
170
+ <p class="fine-print">Yellow被覆量の指定割合を選択色へ置き換えます。0%では変更しません。</p>
171
+ </div>
172
+ </details>
173
+
174
+ <details id="settings-split" class="settings-section">
175
+ <summary><span>分割</span><small>XYタイルとZプレート</small></summary>
176
+ <div class="settings-body">
177
+ <div class="section-actions"><button class="text-button section-reset" type="button">初期値へ戻す</button></div>
178
+ <div class="form-grid split-grid">
179
+ <label>タイル数<input name="tile_count" type="number" min="1" step="1" value="<%= h @values['tile_count'] %>" data-default="<%= h @values['tile_count'] %>" required></label>
180
+ <label>配置
181
+ <select name="tile_layout" id="tile-layout" data-default="<%= h @values['tile_layout'] %>">
182
+ <% %w[auto columns rows grid].each do |layout| %>
183
+ <option value="<%= layout %>" <%= "selected" if selected?(:tile_layout, layout) %>><%= layout %></option>
184
+ <% end %>
185
+ </select>
186
+ </label>
187
+ <label class="grid-only">行数<input name="tile_rows" type="number" min="1" step="1" value="<%= h @values['tile_rows'] %>" data-default="<%= h @values['tile_rows'] %>"></label>
188
+ <label class="grid-only">列数<input name="tile_columns" type="number" min="1" step="1" value="<%= h @values['tile_columns'] %>" data-default="<%= h @values['tile_columns'] %>"></label>
189
+ <label class="check-label z-parts-toggle"><input id="separate-z-parts" name="separate_z_parts" type="checkbox" value="1" data-default="<%= checked?(:separate_z_parts) ? '1' : '0' %>" <%= "checked" if checked?(:separate_z_parts) %>>Z方向を別プレートに分割</label>
190
+ <label id="color-region-field">最大Zプレート数<input id="color-region-parts" name="color_region_parts" type="number" min="1" max="3" step="1" value="<%= h @values['color_region_parts'] %>" data-default="<%= h @values['color_region_parts'] %>" required></label>
191
+ </div>
192
+ <p id="z-parts-summary" class="fine-print"></p>
193
+ <div id="z-parts-diagram" class="z-parts-diagram" aria-label="Z方向のプレート構成"></div>
194
+ </div>
195
+ </details>
196
+
197
+ <details id="settings-advanced" class="settings-section">
198
+ <summary><span>詳細設定</span><small>厚み・余白・ノズル</small></summary>
199
+ <div class="settings-body">
200
+ <div class="section-actions"><button class="text-button section-reset" type="button">初期値へ戻す</button></div>
201
+ <div class="form-grid">
202
+ <label>最小厚み <span>mm</span><input name="minimum_thickness" type="number" min="0.01" step="0.01" value="<%= h @values['minimum_thickness'] %>" data-default="<%= h @values['minimum_thickness'] %>" required></label>
203
+ <label>最大厚み <span>mm</span><input name="maximum_thickness" type="number" min="0.01" step="0.01" value="<%= h @values['maximum_thickness'] %>" data-default="<%= h @values['maximum_thickness'] %>" required></label>
204
+ <label>ノズル <span>mm</span>
205
+ <select name="nozzle_diameter" data-default="<%= h @values['nozzle_diameter'] %>">
206
+ <option value="0.2" <%= "selected" if selected?(:nozzle_diameter, 0.2) %>>0.2</option>
207
+ <option value="0.4" <%= "selected" if selected?(:nozzle_diameter, 0.4) %>>0.4</option>
208
+ </select>
209
+ </label>
210
+ </div>
211
+ <div class="quad-grid">
212
+ <% { "margin_top" => "上余白", "margin_right" => "右余白", "margin_bottom" => "下余白", "margin_left" => "左余白" }.each do |key, label| %>
213
+ <label><%= label %> <span>mm</span><input name="<%= key %>" type="number" min="0" step="0.1" value="<%= h @values[key] %>" data-default="<%= h @values[key] %>" required></label>
214
+ <% end %>
215
+ </div>
216
+ </div>
217
+ </details>
218
+
219
+ </form>
220
+ </section>
221
+
222
+ <section class="panel preview-panel">
223
+ <div class="section-heading compact-heading">
224
+ <div><p class="step">PREVIEW</p><h2>入力プレビュー</h2></div>
225
+ <span id="preview-dimensions" class="dimension-badge"><%= h @values['width'] %> × <%= h @values['height'] %> mm</span>
226
+ </div>
227
+ <div class="preview-toolbar" data-viewer="source-preview">
228
+ <button type="button" data-view-action="out" aria-label="縮小">−</button><output data-view-scale>全体</output>
229
+ <button type="button" data-view-action="in" aria-label="拡大">+</button><button type="button" data-view-action="actual">100%</button><button type="button" data-view-action="reset">全体表示</button>
230
+ <button type="button" data-expand-viewer="source-preview" disabled>大きく表示</button>
231
+ <label class="overlay-toggle"><input id="toggle-tile-overlay" type="checkbox">タイル境界</label>
232
+ <label>背景<select data-view-background><option value="checker">透明</option><option value="light">白</option><option value="gray">灰</option><option value="dark">暗色</option></select></label>
233
+ </div>
234
+ <div id="source-preview" class="image-stage preview-viewport" data-background="checker">
235
+ <img id="source-image" alt="選択した入力画像" draggable="false" hidden>
236
+ <img id="tile-overlay" class="tile-overlay" alt="タイル境界" draggable="false" hidden>
237
+ <p id="source-placeholder" role="status" aria-live="polite">画像未選択</p>
238
+ </div>
239
+ <p class="fine-print"><span data-view-hint="source-preview">拡大するとドラッグで表示位置を移動できます。</span> 倍率は画面表示だけに作用し、印刷寸法を変更しません。</p>
240
+ </section>
241
+ </div>
242
+
243
+ </section>
244
+
245
+ <section id="view-jobs" class="app-view" data-app-view="jobs" aria-labelledby="jobs-view-title" <%= "hidden" unless @initial_view == "jobs" %>>
246
+ <h1 id="jobs-view-title" class="view-title" tabindex="-1">ジョブ</h1>
247
+ <div class="jobs-workspace">
248
+ <section id="job-panel" class="panel job-panel" <%= "hidden" unless @selected_job_id %>>
249
+ <button id="back-to-job-list" class="text-button back-to-job-list" type="button">← 一覧へ戻る</button>
250
+ <div class="section-heading"><div><p class="step">JOB DETAIL</p><h2>表示中の変換ジョブ</h2></div><span id="job-state" class="state-pill" aria-live="polite">待機中</span></div>
251
+ <div class="job-meta"><div><p id="job-view-name" class="job-view-name">ジョブを読み込んでいます</p><p id="job-id" class="job-id"><%= h @selected_job_id %></p></div><p id="job-settings-snapshot" class="fine-print"></p></div>
252
+ <div class="progress-track" role="progressbar" aria-label="変換進捗" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><span id="progress-bar"></span></div>
253
+ <div class="job-copy"><strong id="job-percent">0%</strong><span id="job-message">変換を開始しています</span></div>
254
+ <div class="job-freshness"><span id="job-connection" role="status" aria-live="polite">状態を取得しています</span><span id="job-updated-at"></span><button id="retry-status-button" class="text-button" type="button" hidden>状態を再取得</button></div>
255
+ <div class="job-actions"><button id="cancel-button" class="secondary-button" type="button">停止</button><button id="load-job-settings" class="secondary-button" type="button" hidden>作成に適用</button><button id="delete-button" class="danger-button" type="button" hidden>ジョブを削除</button></div>
256
+ <p id="delete-summary" class="fine-print" hidden></p>
257
+ <div id="job-detail-tabs" class="job-detail-tabs" role="tablist" aria-label="ジョブ詳細" hidden>
258
+ <button type="button" role="tab" aria-selected="true" aria-controls="job-preview-panel" data-job-tab="preview">プレビュー</button>
259
+ <button type="button" role="tab" aria-selected="false" aria-controls="job-artifacts-panel" data-job-tab="artifacts">成果物</button>
260
+ <button type="button" role="tab" aria-selected="false" aria-controls="job-settings-panel" data-job-tab="settings">設定・ログ</button>
261
+ </div>
262
+ <div id="result-layout" class="result-layout" hidden>
263
+ <section id="job-preview-panel" data-job-panel="preview" role="tabpanel">
264
+ <div id="result-tabs" class="result-tabs" role="tablist" aria-label="プレビュー種類"></div>
265
+ <div class="preview-toolbar" data-viewer="result-preview">
266
+ <button type="button" data-view-action="out" aria-label="縮小">−</button><output data-view-scale>全体</output>
267
+ <button type="button" data-view-action="in" aria-label="拡大">+</button><button type="button" data-view-action="actual">100%</button><button type="button" data-view-action="reset">全体表示</button>
268
+ <button type="button" data-expand-viewer="result-preview">大きく比較</button>
269
+ <label>背景<select data-view-background><option value="checker">透明</option><option value="light">白</option><option value="gray">灰</option><option value="dark">暗色</option></select></label>
270
+ </div>
271
+ <div class="comparison-grid">
272
+ <figure id="corrected-input-figure"><figcaption>補正後入力</figcaption><div id="compare-source-preview" class="result-preview comparison-source preview-viewport" data-viewer-group="result-preview"><img id="compare-source-image" alt="変換時の入力プレビュー" draggable="false"></div></figure>
273
+ <figure><figcaption id="result-caption">CMYW</figcaption><div id="result-preview" class="result-preview preview-viewport" data-background="checker"><img id="result-image" alt="生成されたプレビュー" draggable="false"></div></figure>
274
+ </div>
275
+ <p id="corrected-input-unavailable" class="notice info" hidden>この旧ジョブには補正後入力が保存されていないため、生成結果だけを表示します。</p>
276
+ <p class="fine-print"><span data-view-hint="result-preview">拡大すると2画像を同じ倍率・位置で移動できます。</span></p>
277
+ <p id="result-legend" class="fine-print">材料マスクは白が0層、濃い部分ほどその材料の層数が多い領域です。</p>
278
+ </section>
279
+ <section id="job-artifacts-panel" data-job-panel="artifacts" role="tabpanel" hidden>
280
+ <section id="tile-results" class="tile-results" hidden>
281
+ <div class="section-heading compact-heading"><div><h3>分割モデル</h3><p class="fine-print">各プレビューは対応する3MFの範囲です。</p></div></div>
282
+ <div id="tile-result-list" class="tile-result-list"></div>
283
+ </section>
284
+ <div class="artifact-section"><div class="artifact-heading"><h3>成果物</h3><div><button id="download-models" class="secondary-button" type="button">全3MFをZIP</button><button id="download-all" class="secondary-button" type="button">全成果物をZIP</button></div></div><p id="bambu-open-status" class="artifact-status fine-print" role="status" aria-live="polite"></p><ul id="artifact-list" class="artifact-list"></ul></div>
285
+ </section>
286
+ <section id="job-settings-panel" data-job-panel="settings" role="tabpanel" hidden>
287
+ <h3>生成時の設定</h3>
288
+ <dl id="job-setting-details" class="job-setting-details"></dl>
289
+ <p class="fine-print">この設定を作成に適用しても、現在選択中の画像は維持されます。</p>
290
+ <a id="job-detail-log-link" class="text-button" href="#" target="_blank" rel="noopener" hidden>ローカルログを開く</a>
291
+ </section>
292
+ </div>
293
+ <div id="job-error" class="notice error" hidden><p id="job-error-message"></p><div class="recovery-actions"><button id="choose-another-image" class="text-button" type="button">画像を選び直す</button><button id="review-settings" class="text-button" type="button">設定を確認する</button><a id="job-log-link" class="text-button" href="#" target="_blank" rel="noopener" hidden>ログを確認する</a></div></div>
294
+ </section>
295
+
296
+ <aside class="panel history-panel">
297
+ <div class="section-heading compact-heading">
298
+ <div><p class="step">RECENT</p><h2>最近のジョブ</h2></div>
299
+ </div>
300
+ <div class="history-controls">
301
+ <label>検索<input id="history-query" type="search" placeholder="入力名・ジョブID"></label>
302
+ <label>状態<select id="history-filter"><option value="all">すべて</option><option value="active">実行中</option><option value="completed">完了</option><option value="failed">失敗・停止</option></select></label>
303
+ <label>開始日<input id="history-date-from" type="date"></label>
304
+ <label>終了日<input id="history-date-to" type="date"></label>
305
+ <label>並び順<select id="history-sort"><option value="created_desc">新しい順</option><option value="created_asc">古い順</option><option value="name_asc">入力名 A–Z</option><option value="name_desc">入力名 Z–A</option></select></label>
306
+ </div>
307
+ <div class="history-selection-toolbar">
308
+ <div class="history-selection-summary">
309
+ <span id="history-selection-status" aria-live="polite">未選択</span>
310
+ <button id="select-all-jobs" class="text-button" type="button" aria-pressed="false" disabled>全てを選択する</button>
311
+ </div>
312
+ <div class="history-selection-actions">
313
+ <button id="compare-jobs" class="secondary-button" type="button" disabled>2件を比較</button>
314
+ <button id="delete-selected-jobs" class="danger-button" type="button" disabled>選択したジョブを削除</button>
315
+ </div>
316
+ </div>
317
+ <p id="history-empty" class="empty-copy" <%= "hidden" unless @jobs.empty? %>>まだジョブはありません。</p>
318
+ <ol id="job-list" class="job-list">
319
+ <% @jobs.each do |job| %>
320
+ <li data-state="<%= h job['state'] %>">
321
+ <a href="/?job=<%= h job['job_id'] %>"><strong><%= h(job['input_name'] || job['job_id']) %></strong><span><%= h job['job_id'] %></span><small><%= h job['state'] %> · <%= h job['percent'] %>%</small></a>
322
+ <% if Lithop::JobExecution::FINAL_STATES.include?(job['state']) %>
323
+ <button class="history-delete" type="button" data-job-id="<%= h job['job_id'] %>" data-file-count="<%= h job['file_count'] %>" data-size-bytes="<%= h job['size_bytes'] %>">削除</button>
324
+ <% end %>
325
+ </li>
326
+ <% end %>
327
+ </ol>
328
+ <button id="load-more-jobs" class="secondary-button load-more" type="button">さらに表示</button>
329
+ </aside>
330
+ </div>
331
+ </section>
332
+
333
+ <section id="view-presets" class="app-view" data-app-view="presets" aria-labelledby="presets-view-title" <%= "hidden" unless @initial_view == "presets" %>>
334
+ <h1 id="presets-view-title" class="view-title" tabindex="-1">プリセット</h1>
335
+ <div class="presets-workspace">
336
+ <aside class="panel preset-list-panel" aria-labelledby="preset-list-title">
337
+ <div class="section-heading compact-heading">
338
+ <div><p class="step">SAVED SETTINGS</p><h2 id="preset-list-title">保存設定</h2></div>
339
+ </div>
340
+ <label class="preset-select-label">名前付きプリセット
341
+ <select id="preset-select"><option value="">プリセットを選択</option></select>
342
+ </label>
343
+ <button id="preset-load" class="secondary-button preset-apply-button" type="button">作成に適用</button>
344
+ <div class="draft-apply">
345
+ <strong>下書き</strong>
346
+ <small>最後に保存した1件の編集設定</small>
347
+ <button id="draft-restore" class="secondary-button" type="button">作成に適用</button>
348
+ </div>
349
+ </aside>
350
+ <section id="settings-library-section" class="panel preset-panel">
351
+ <div class="section-heading">
352
+ <div><p class="step">MANAGE SETTINGS</p><h2>保存と管理</h2></div>
353
+ </div>
354
+ <p class="preset-intro">現在の編集設定を保存・再利用します。入力画像、ジョブID、成果物パスは保存しません。</p>
355
+ <div class="settings-library">
356
+ <label>プリセット名<input id="preset-name" type="text" maxlength="80" autocomplete="off" placeholder="例: 細線 0.2 mm"></label>
357
+ <div class="library-actions">
358
+ <button id="preset-save" class="secondary-button" type="button">現在設定を保存</button>
359
+ <button id="preset-rename" class="text-button" type="button">名前変更</button>
360
+ <button id="preset-delete" class="text-button danger-text" type="button">削除</button>
361
+ </div>
362
+ <div class="library-actions draft-actions">
363
+ <button id="draft-save" class="secondary-button" type="button">下書き保存</button>
364
+ <button id="draft-delete" class="text-button danger-text" type="button">下書きを削除</button>
365
+ </div>
366
+ <p id="settings-library-status" class="fine-print" role="status" aria-live="polite">画像は保存しません。復元後に画像を選択してください。</p>
367
+ </div>
368
+ </section>
369
+ </div>
370
+ </section>
371
+ </div>
372
+ </div>
373
+ </main>
374
+
375
+ <dialog id="preview-dialog" class="preview-dialog" aria-labelledby="preview-dialog-title">
376
+ <div class="dialog-heading"><div><p class="step">LARGE PREVIEW</p><h2 id="preview-dialog-title">プレビューを大きく表示</h2></div><button id="close-preview-dialog" class="secondary-button" type="button">閉じる</button></div>
377
+ <div class="preview-toolbar" data-viewer="dialog-preview">
378
+ <button type="button" data-view-action="out" aria-label="縮小">−</button><output data-view-scale>全体</output>
379
+ <button type="button" data-view-action="in" aria-label="拡大">+</button><button type="button" data-view-action="actual">100%</button><button type="button" data-view-action="reset">全体表示</button>
380
+ <label>背景<select data-view-background><option value="checker">透明</option><option value="light">白</option><option value="gray">灰</option><option value="dark">暗色</option></select></label>
381
+ </div>
382
+ <div id="dialog-preview-grid" class="dialog-preview-grid">
383
+ <figure id="dialog-source-figure"><figcaption id="dialog-source-caption">入力</figcaption><div id="dialog-source-preview" class="result-preview preview-viewport" data-viewer-group="dialog-preview" data-background="checker"><img id="dialog-source-image" alt="拡大表示する入力" draggable="false"></div></figure>
384
+ <figure id="dialog-result-figure"><figcaption id="dialog-result-caption">生成結果</figcaption><div id="dialog-result-preview" class="result-preview preview-viewport" data-viewer-group="dialog-preview" data-background="checker"><img id="dialog-result-image" alt="拡大表示する生成結果" draggable="false"></div></figure>
385
+ </div>
386
+ <p id="dialog-legend" class="fine-print" hidden></p>
387
+ <p class="fine-print"><span data-view-hint="dialog-preview">拡大するとドラッグまたは矢印キーで移動できます。</span> Escでも閉じられます。</p>
388
+ </dialog>
389
+
390
+ <dialog id="job-compare-dialog" class="preview-dialog compare-dialog" aria-labelledby="job-compare-title">
391
+ <div class="dialog-heading"><div><p class="step">JOB COMPARISON</p><h2 id="job-compare-title">2つのジョブを比較</h2></div><button id="close-job-compare" class="secondary-button" type="button">閉じる</button></div>
392
+ <div class="preview-toolbar" data-viewer="job-compare-preview">
393
+ <button type="button" data-view-action="out" aria-label="縮小">−</button><output data-view-scale>全体</output>
394
+ <button type="button" data-view-action="in" aria-label="拡大">+</button><button type="button" data-view-action="actual">100%</button><button type="button" data-view-action="reset">全体表示</button>
395
+ <label>画像<select id="job-compare-kind"></select></label>
396
+ <label>背景<select data-view-background><option value="checker">透明</option><option value="light">白</option><option value="gray">灰</option><option value="dark">暗色</option></select></label>
397
+ </div>
398
+ <p id="job-compare-status" class="notice info" role="status" hidden></p>
399
+ <div class="job-compare-grid">
400
+ <% %w[a b].each do |side| %>
401
+ <figure><figcaption><strong id="compare-<%= side %>-name"></strong><span id="compare-<%= side %>-meta"></span></figcaption><div id="compare-<%= side %>-preview" class="result-preview preview-viewport" data-viewer-group="job-compare-preview" data-background="checker"><img id="compare-<%= side %>-image" alt="比較する生成結果 <%= side.upcase %>" draggable="false"><p id="compare-<%= side %>-missing" hidden>この画像はありません</p></div><button id="compare-<%= side %>-use" class="text-button" type="button">作成に適用</button></figure>
402
+ <% end %>
403
+ </div>
404
+ <section class="compare-settings"><h3>仕上がりに関係する設定差</h3><div class="table-scroll"><table><thead><tr><th>項目</th><th>A</th><th>B</th></tr></thead><tbody id="job-compare-differences"></tbody></table></div></section>
405
+ <p class="fine-print">画面上の比較は実測した透過色を保証しません。異なる寸法・縦横比はそれぞれの比率で表示します。</p>
406
+ </dialog>
407
+
408
+ <dialog id="help-dialog" class="help-dialog" aria-labelledby="help-dialog-title">
409
+ <div class="dialog-heading"><div><p class="step">LOCAL HELP</p><h2 id="help-dialog-title">画面の使い方</h2></div><button id="close-help-dialog" class="secondary-button" type="button">閉じる</button></div>
410
+ <ol>
411
+ <li><strong>作成</strong><span>画像を選び、設定と入力プレビューを確認して変換します。</span></li>
412
+ <li><strong>ジョブ</strong><span>進捗、生成結果、分割成果物、ログを確認します。</span></li>
413
+ <li><strong>プリセット</strong><span>現在の編集設定を名前付きで保存し、後から再利用します。</span></li>
414
+ </ol>
415
+ <p class="fine-print">処理中は上部のジョブ表示から進捗確認と停止ができます。画像と成果物は外部サービスへ送信されません。</p>
416
+ </dialog>
417
+ </body>
418
+ </html>
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "lithop"
5
+
6
+ module Lithop
7
+ module Web
8
+ # Runs one UI conversion in a separate Ruby process.
9
+ module Worker
10
+ module_function
11
+
12
+ def run(arguments, out: $stdout, err: $stderr)
13
+ job_id, input_path, settings_path, project_dir = arguments
14
+ raise Error, "invalid web worker arguments" unless arguments.length == 4
15
+
16
+ loaded = SettingsFile.load(settings_path, required: true)
17
+ settings = Settings.new(**loaded.values)
18
+ result = Export.new(settings:, project_dir:, job_id:).call(input_path)
19
+ out.puts "Generated #{result.three_mf_triangle_count} CMYW 3MF triangles in #{result.path}"
20
+ 0
21
+ rescue Error, SystemCallError, JSON::ParserError => e
22
+ err.puts "lithop: #{e.message}"
23
+ 1
24
+ end
25
+ end
26
+ end
27
+ end
data/lib/lithop.rb CHANGED
@@ -8,6 +8,7 @@ module Lithop
8
8
  class InputError < Error; end
9
9
  end
10
10
 
11
+ require_relative "lithop/native"
11
12
  require_relative "lithop/sampling_grid"
12
13
  require_relative "lithop/layer_structure"
13
14
  require_relative "lithop/settings"
@@ -20,7 +21,7 @@ require_relative "lithop/model/palette"
20
21
  require_relative "lithop/model/material_display_map"
21
22
  require_relative "lithop/model/filament_profile"
22
23
  require_relative "lithop/model/material_model"
23
- require_relative "lithop/model/layer_region_model"
24
+ require_relative "lithop/model/z_region_model"
24
25
  require_relative "lithop/tiling"
25
26
  require_relative "lithop/image/placement"
26
27
  require_relative "lithop/image/svg_loader"
@@ -52,6 +53,7 @@ require_relative "lithop/export/print_readme"
52
53
  require_relative "lithop/export/previews"
53
54
  require_relative "lithop/export/generation"
54
55
  require_relative "lithop/export/job"
56
+ require_relative "lithop/cli/image_adjustment_options"
55
57
  require_relative "lithop/cli/image_options"
56
58
  require_relative "lithop/cli/options"
57
59
  require_relative "lithop/cli/settings"
data/sig/lithop.rbs CHANGED
@@ -1,6 +1,30 @@
1
1
  module Lithop
2
2
  VERSION: String
3
3
 
4
+ module Native
5
+ class MeshBuilder
6
+ def self.new: () -> MeshBuilder
7
+ def add_batch: (Array[Float] coordinates, Array[Integer] topology_keys) -> Integer
8
+ def vertex_count: () -> Integer
9
+ def triangle_count: () -> Integer
10
+ def center: () -> Array[Float]
11
+ def vertex_xml: (Integer offset, Integer count, Array[Float] origin) -> String
12
+ def triangle_xml: (Integer offset, Integer count) -> String
13
+ def memory_bytes: () -> Integer
14
+ end
15
+
16
+ def self.available?: () -> bool
17
+ def self.enabled?: () -> bool
18
+ def self.backend: () -> (:rust | :ruby)
19
+ def self.extension_version: () -> String
20
+ def self.loaded_extension_version: () -> String?
21
+ def self.diagnostics: () -> Hash[Symbol, untyped]
22
+ def self.palette_indices: (Array[Float] cmys, Array[Float] coverages) -> Array[Integer]
23
+ def self.vertex_xml: (Array[Float] vertices, Array[Float] origin) -> String
24
+ def self.triangle_xml: (Array[Integer] indices) -> String
25
+ def self.column_mesh: (Integer columns, Integer rows, Array[Float] x_boundaries, Array[Float] y_boundaries, Array[Float] z_boundaries, Array[Integer] intervals, Array[Integer] group_ids, Integer group_count) -> MeshBuilder
26
+ end
27
+
4
28
  class Error < StandardError
5
29
  end
6
30
 
@@ -25,6 +49,9 @@ module Lithop
25
49
  attr_reader brightness: Numeric
26
50
  attr_reader contrast: Numeric
27
51
  attr_reader saturation: Numeric
52
+ attr_reader suppress_pale_colors: bool
53
+ attr_reader yellow_mix_color: String
54
+ attr_reader yellow_mix_percent: Numeric
28
55
  attr_reader minimum_thickness: Numeric
29
56
  attr_reader maximum_thickness: Numeric
30
57
  attr_reader pixel_size: Numeric
@@ -64,6 +91,19 @@ module Lithop
64
91
  def resolved_tile_shape: () -> [Integer, Integer]
65
92
  def resolved_tile_rows: () -> Integer
66
93
  def resolved_tile_columns: () -> Integer
94
+ def resolved_z_part_boundaries: () -> Array[Float]
95
+ def resolved_z_part_ranges: () -> Array[Range[Float]]
96
+ def resolved_z_color_ranges: () -> Array[Range[Float]]
97
+ def resolved_z_part_count: () -> Integer
98
+ def z_separated_minimum_thickness: () -> Float
99
+ def z_separated_source_end: () -> Float
100
+ def z_part_base_white_layers: (?Integer index) -> Integer
101
+ def z_part_base_white_thickness: (?Integer index) -> Float
102
+ def z_part_base_white_layers_by_plate: () -> Array[Integer]
103
+ def z_part_base_white_thicknesses: () -> Array[Float]
104
+ def z_part_minimum_build_thickness: () -> Float
105
+ def z_separated_output_minimum_thickness: () -> Float
106
+ def z_separated_output_maximum_thickness: () -> Float
67
107
  def estimated_triangles: () -> Integer
68
108
  def triangle_limit_message: (String label, Integer triangle_count) -> String
69
109
  def x_boundaries: () -> Array[Numeric]
@@ -121,6 +161,7 @@ module Lithop
121
161
 
122
162
  def initialize: (?layer_count: Integer, ?pigment_order: Array[Symbol]) -> void
123
163
  def nearest: (Array[Numeric] cmy) -> Recipe
164
+ def nearest_many: (Array[Array[Numeric]] cmys) -> Array[Recipe]
124
165
  def display_color: (Recipe recipe) -> String
125
166
  end
126
167