feat(O): UI-Vollständigkeit + v3-Workflows + OCC-Kantenanalyse
Backend:
- Phase I: notification_configs router (GET/PUT/{event}/{channel}/POST reset)
war bereits in notifications.py — add-alias endpoint in uploads.py ergänzt
- OutputType schema: workflow_definition_id + workflow_name fields;
PATCH unterstützt Workflow-Zuweisung; _enrich_workflow_names() batch query
- Dispatch-Integration: orders.py dispatch_renders() → dispatch_render_with_workflow()
mit Legacy-Fallback; neues Logging
- uploads.py: POST /validations/{id}/add-alias für Material-Lücken
Pipeline:
- step_processor.py: extract_mesh_edge_data() via OCC — berechnet Dihedralwinkel
aller Kanten, liefert suggested_smooth_angle + sharp_edge_midpoints
Integriert in extract_cad_metadata() und process_cad_file()
- domains/rendering/tasks.py: apply_asset_library_materials_task (K3),
export_gltf_for_order_line_task → Blender export_gltf.py (K4),
export_blend_for_order_line_task → export_blend.py fix (K5)
- render-worker/scripts/still_render.py: _mark_sharp_and_seams() mit
OCC midpoint KD-tree matching + UV-Seam-Markierung
- render-worker/scripts/blender_render.py: identische Funktion + mesh_attributes parsing
Frontend:
- Layout.tsx: Upload-Link in Sidebar (alle User); Asset Libraries Link (admin/PM)
- App.tsx: /asset-libraries Route
- AssetLibrary.tsx: neue Seite (Upload, Catalog-Anzeige, Refresh, Toggle, Delete)
- OutputTypeTable.tsx: Workflow-Dropdown + Legacy/Workflow Badge
- ProductDetail.tsx: Geometry-Karte (Volumen, Surface, BBox, Sharp-Winkel)
- api/outputTypes.ts + api/products.ts: neue Felder
- api/imports.ts: ImportValidation API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -98,6 +98,16 @@ denoising_prefilter_arg = argv[22] if len(argv) > 22 else ""
|
||||
denoising_quality_arg = argv[23] if len(argv) > 23 else ""
|
||||
denoising_use_gpu_arg = argv[24] if len(argv) > 24 else ""
|
||||
|
||||
# Named argument: --mesh-attributes <json>
|
||||
_mesh_attrs: dict = {}
|
||||
_sys_argv = sys.argv
|
||||
if "--mesh-attributes" in _sys_argv:
|
||||
_idx = _sys_argv.index("--mesh-attributes")
|
||||
try:
|
||||
_mesh_attrs = _json.loads(_sys_argv[_idx + 1])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Validate template path: if provided it MUST exist on disk.
|
||||
# Fail loudly rather than silently rendering with factory settings.
|
||||
if template_path and not os.path.isfile(template_path):
|
||||
@@ -203,6 +213,69 @@ def _apply_rotation(parts, rx, ry, rz):
|
||||
print(f"[blender_render] applied rotation ({rx}°, {ry}°, {rz}°) to {len(parts)} parts")
|
||||
|
||||
|
||||
def _mark_sharp_and_seams(obj, smooth_angle_deg: float, sharp_edge_midpoints=None):
|
||||
"""Mark sharp edges and UV seams based on angle threshold and optional midpoints."""
|
||||
import math
|
||||
import bpy
|
||||
|
||||
# Ensure we're working with the right object
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
|
||||
# Set auto-smooth angle
|
||||
if hasattr(obj.data, 'auto_smooth_angle'):
|
||||
obj.data.auto_smooth_angle = math.radians(smooth_angle_deg)
|
||||
|
||||
# Enter edit mode to mark edges
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
|
||||
# Select edges above threshold angle and mark sharp
|
||||
bpy.ops.mesh.edges_select_sharp(sharpness=math.radians(smooth_angle_deg))
|
||||
bpy.ops.mesh.mark_sharp()
|
||||
|
||||
# Mark same edges as UV seams
|
||||
bpy.ops.mesh.mark_seam(clear=False)
|
||||
|
||||
# If we have OCC-derived midpoints, try to mark additional edges
|
||||
if sharp_edge_midpoints and len(sharp_edge_midpoints) > 0:
|
||||
try:
|
||||
import bmesh
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
bm.edges.ensure_lookup_table()
|
||||
bm.verts.ensure_lookup_table()
|
||||
|
||||
# Build KD-tree for edge midpoints
|
||||
import mathutils
|
||||
kd = mathutils.kdtree.KDTree(len(bm.edges))
|
||||
for i, edge in enumerate(bm.edges):
|
||||
midpt = (edge.verts[0].co + edge.verts[1].co) / 2
|
||||
kd.insert(midpt, i)
|
||||
kd.balance()
|
||||
|
||||
# For each OCC sharp midpoint, find nearest Blender edge
|
||||
tol = 0.5 # 0.5 mm tolerance (coordinates in mm before scale)
|
||||
for mp in sharp_edge_midpoints[:200]:
|
||||
vec = mathutils.Vector(mp)
|
||||
co, idx, dist = kd.find(vec)
|
||||
if dist < tol:
|
||||
bm.edges[idx].seam = True
|
||||
try:
|
||||
bm.edges[idx].smooth = False
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
bm.to_mesh(obj.data)
|
||||
bm.free()
|
||||
except Exception:
|
||||
pass # Non-fatal
|
||||
|
||||
# Return to object mode
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
|
||||
def _import_stl(stl_file):
|
||||
"""Import STL into Blender, using per-part STLs if available.
|
||||
|
||||
@@ -394,9 +467,13 @@ if use_template:
|
||||
col.objects.unlink(part)
|
||||
target_col.objects.link(part)
|
||||
|
||||
# Apply smooth shading
|
||||
# Apply smooth shading and mark sharp edges / UV seams
|
||||
for part in parts:
|
||||
_apply_smooth(part, smooth_angle)
|
||||
_mark_sharp_and_seams(
|
||||
part, smooth_angle,
|
||||
sharp_edge_midpoints=_mesh_attrs.get('sharp_edge_midpoints'),
|
||||
)
|
||||
|
||||
# Material assignment: library materials if available, otherwise palette
|
||||
if material_library_path and material_map:
|
||||
@@ -469,6 +546,10 @@ else:
|
||||
|
||||
for i, part in enumerate(parts):
|
||||
_apply_smooth(part, smooth_angle)
|
||||
_mark_sharp_and_seams(
|
||||
part, smooth_angle,
|
||||
sharp_edge_midpoints=_mesh_attrs.get('sharp_edge_midpoints'),
|
||||
)
|
||||
_assign_palette_material(part, i)
|
||||
|
||||
# Apply material library on top of palette colours (same logic as Mode B).
|
||||
|
||||
@@ -145,6 +145,70 @@ def _apply_mesh_attributes(objects: list, mesh_attributes: dict) -> None:
|
||||
obj.data.auto_smooth_angle = threshold_rad
|
||||
|
||||
|
||||
def _mark_sharp_and_seams(obj, smooth_angle_deg: float, sharp_edge_midpoints=None):
|
||||
"""Mark sharp edges and UV seams based on angle threshold and optional midpoints."""
|
||||
import math
|
||||
import bpy
|
||||
|
||||
# Ensure we're working with the right object
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
|
||||
# Set auto-smooth angle
|
||||
if hasattr(obj.data, 'auto_smooth_angle'):
|
||||
obj.data.auto_smooth_angle = math.radians(smooth_angle_deg)
|
||||
|
||||
# Enter edit mode to mark edges
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
|
||||
# Select edges above threshold angle and mark sharp
|
||||
bpy.ops.mesh.edges_select_sharp(sharpness=math.radians(smooth_angle_deg))
|
||||
bpy.ops.mesh.mark_sharp()
|
||||
|
||||
# Mark same edges as UV seams
|
||||
bpy.ops.mesh.mark_seam(clear=False)
|
||||
|
||||
# If we have OCC-derived midpoints, try to mark additional edges
|
||||
if sharp_edge_midpoints and len(sharp_edge_midpoints) > 0:
|
||||
try:
|
||||
import bmesh
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
bm.edges.ensure_lookup_table()
|
||||
bm.verts.ensure_lookup_table()
|
||||
|
||||
# Build KD-tree for edge midpoints
|
||||
import mathutils
|
||||
kd = mathutils.kdtree.KDTree(len(bm.edges))
|
||||
for i, edge in enumerate(bm.edges):
|
||||
midpt = (edge.verts[0].co + edge.verts[1].co) / 2
|
||||
kd.insert(midpt, i)
|
||||
kd.balance()
|
||||
|
||||
# For each OCC sharp midpoint, find nearest Blender edge
|
||||
tol = 0.5 # 0.5 mm tolerance (coordinates in mm before scale)
|
||||
for mp in sharp_edge_midpoints[:200]:
|
||||
vec = mathutils.Vector(mp)
|
||||
co, idx, dist = kd.find(vec)
|
||||
if dist < tol:
|
||||
bm.edges[idx].seam = True
|
||||
# Mark sharp via custom attribute
|
||||
try:
|
||||
bm.edges[idx].smooth = False
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
bm.to_mesh(obj.data)
|
||||
bm.free()
|
||||
except Exception:
|
||||
pass # Non-fatal
|
||||
|
||||
# Return to object mode
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
|
||||
def _import_stl(stl_file):
|
||||
"""Import STL into Blender, using per-part STLs if available.
|
||||
|
||||
@@ -411,9 +475,13 @@ def main():
|
||||
col.objects.unlink(part)
|
||||
target_col.objects.link(part)
|
||||
|
||||
# Apply smooth shading
|
||||
# Apply smooth shading and mark sharp edges / UV seams
|
||||
for part in parts:
|
||||
_apply_smooth(part, SMOOTH_ANGLE)
|
||||
_mark_sharp_and_seams(
|
||||
part, SMOOTH_ANGLE,
|
||||
sharp_edge_midpoints=_mesh_attrs.get('sharp_edge_midpoints'),
|
||||
)
|
||||
|
||||
# Material assignment: library materials if available, otherwise palette
|
||||
if material_library_path and material_map:
|
||||
@@ -504,6 +572,10 @@ def main():
|
||||
|
||||
for i, part in enumerate(parts):
|
||||
_apply_smooth(part, SMOOTH_ANGLE)
|
||||
_mark_sharp_and_seams(
|
||||
part, SMOOTH_ANGLE,
|
||||
sharp_edge_midpoints=_mesh_attrs.get('sharp_edge_midpoints'),
|
||||
)
|
||||
|
||||
# Material assignment: library materials if available, else part_colors/palette
|
||||
if material_library_path and material_map:
|
||||
|
||||
Reference in New Issue
Block a user