// mesh_convert.go implements meshgen.ConverterProvider against the // mediautils shim's POST /v1/convert_mesh (multipart file + target), // reached through the /upstream passthrough (ADR-0020). Exists because // Hunyuan3D's api_server always returns GLB — STL for the printer // pipeline is produced by this conversion hop. package llamaswap import ( "context" "fmt" "net/http" "strings" "gitea.stevedudenhoeffer.com/steve/majordomo/llm" "gitea.stevedudenhoeffer.com/steve/majordomo/meshgen" ) // MeshConverter implements meshgen.ConverterProvider. func (p *Provider) MeshConverter(id string) (meshgen.Converter, error) { if err := p.requireBaseURL(); err != nil { return nil, err } return &meshConverter{p: p, id: id}, nil } type meshConverter struct { p *Provider id string } // Convert implements meshgen.Converter. func (m *meshConverter) Convert(ctx context.Context, mesh meshgen.Mesh, format string) (*meshgen.Result, error) { if len(mesh.Data) == 0 { return nil, fmt.Errorf("%w: mesh conversion requires mesh bytes", llm.ErrUnsupported) } format = strings.ToLower(strings.TrimSpace(format)) mimeType, ok := meshFormats[format] if !ok { return nil, fmt.Errorf("%w: unsupported mesh format %q (want glb, stl, or obj)", llm.ErrUnsupported, format) } path, err := upstreamPath(m.id, "/v1/convert_mesh") if err != nil { return nil, err } filename := "mesh." + mesh.Format if mesh.Format == "" { filename = "mesh" } body, contentType, err := buildMultipart("build mesh-convert form", filePart{field: "file", filename: filename, data: mesh.Data}, []formField{{"target", format, true}}) if err != nil { return nil, err } raw, _, err := m.p.doRaw(ctx, http.MethodPost, path, m.id, contentType, body, maxMeshResponseBytes) if err != nil { return nil, err } if len(raw) == 0 { return nil, &llm.APIError{Provider: m.p.name, Model: m.id, Message: "mesh conversion returned no data"} } actualFormat, actualMIME := sniffMeshFormat(raw, format, mimeType) return &meshgen.Result{Mesh: meshgen.Mesh{Data: raw, Format: actualFormat, MIME: actualMIME}}, nil }