Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Sources/ThreeMF/Resources/Multiproperties.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,11 @@ public struct Multiproperties: Resource, XMLElementCodable {
public extension Multiproperties {
/// One layer of a combination: which property it takes, and how it blends onto what's beneath.
struct Layer: Sendable {
let property: PropertyReference
let blendMethod: BlendMethod
/// Which entry of which property group this layer takes.
public let property: PropertyReference

/// How this layer combines with the ones below it.
public let blendMethod: BlendMethod
}

/// One combination, as layers from the base upward.
Expand Down
9 changes: 8 additions & 1 deletion Sources/ThreeMF/Resources/Object/Alternatives.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ public struct Alternative: XMLElementCodable, Sendable {
/// How exact this alternative is, so a consumer can tell it apart from the others.
public var modelResolution: ModelResolution?

init(objectID: ResourceID, uuid: UUID? = nil, path: URL? = nil, modelResolution: ModelResolution? = nil) {
/// Creates an alternative representation of an object.
/// - Parameters:
/// - objectID: The id of the object holding this representation.
/// - uuid: The identifier this alternative shares with the object it stands in for. A fresh
/// one is generated when you don't give one.
/// - path: The model part the alternative's object lives in, when it isn't this one.
/// - modelResolution: How exact this alternative is.
public init(objectID: ResourceID, uuid: UUID? = nil, path: URL? = nil, modelResolution: ModelResolution? = nil) {
self.objectID = objectID
self.uuid = uuid ?? UUID()
self.path = path
Expand Down
11 changes: 11 additions & 0 deletions Sources/ThreeMF/Resources/Object/Mesh.TriangleSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ public extension Mesh {
///
/// Written as ranges where the indices are contiguous, so a set covering a whole region stays compact.
public var triangleIndices: IndexSet

/// Creates a triangle set.
/// - Parameters:
/// - name: A human-readable name for the set.
/// - identifier: An identifier for the set, unique within its mesh.
/// - triangleIndices: The indices into ``Mesh/triangles`` that belong to this set.
public init(name: String, identifier: String, triangleIndices: IndexSet) {
self.name = name
self.identifier = identifier
self.triangleIndices = triangleIndices
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/ThreeMF/Resources/Object/Object.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ public extension Object {
case other

/// The type an object has when the file doesn't say: ``ObjectType/model``.
static let `default` = Self.model
public static let `default` = Self.model
}
}

Expand Down
6 changes: 5 additions & 1 deletion Sources/ThreeMF/Resources/Texture2DGroup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ public extension Texture2DGroup {
/// The vertical position across the image, from 0 to 1.
public let v: Double

init(u: Double, v: Double) {
/// Creates a texture coordinate.
/// - Parameters:
/// - u: The horizontal position across the image, from 0 to 1.
/// - v: The vertical position across the image, from 0 to 1.
public init(u: Double, v: Double) {
self.u = u
self.v = v
}
Expand Down
70 changes: 70 additions & 0 deletions Tests/Tests/PublicAPIAccessTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Testing
import Foundation
import Nodal

// Deliberately a plain import rather than `@testable`, so this file sees the package exactly as a
// client does. Every symbol below is reachable from the public API; if one of them loses its
// `public` again, this file stops compiling.
import ThreeMF

struct PublicAPIAccessTests {
@Test func `a multiproperties layer reports the property it takes and how it blends`() throws {
let multiproperties = Multiproperties(
id: 3,
propertyGroupIDs: [1, 2],
blendMethods: [.multiply],
multis: [[0, 1]]
)

let layers = try #require(multiproperties.layerSequences.first)
#expect(layers.count == 2)

#expect(layers[0].property == PropertyReference(groupID: 1, index: 0))
#expect(layers[1].property == PropertyReference(groupID: 2, index: 1))
#expect(layers[1].blendMethod == .multiply)
}

@Test func `an object's type falls back to the documented default`() {
let object = Object(id: 1, content: .mesh(Mesh(vertices: [], triangles: [])))

// The file said nothing, which per the spec means `.model`.
#expect(object.type == nil)
#expect(object.type ?? .default == .model)
}

@Test func `a triangle set can be built and handed to a mesh`() {
let set = Mesh.TriangleSet(name: "Top", identifier: "top", triangleIndices: IndexSet(0..<2))
let mesh = Mesh(vertices: [], triangles: [], triangleSets: [set])

#expect(mesh.triangleSets.count == 1)
#expect(mesh.triangleSets[0].name == "Top")
#expect(mesh.triangleSets[0].identifier == "top")
#expect(mesh.triangleSets[0].triangleIndices == IndexSet(0..<2))
}

@Test func `texture coordinates can be built and handed to a group`() {
let group = Texture2DGroup(id: 2, texture2DID: 1, coordinates: [
Texture2DGroup.Coordinate(u: 0, v: 0),
Texture2DGroup.Coordinate(u: 1, v: 0.5),
])

#expect(group.coordinates.count == 2)
#expect(group.coordinates[1].u == 1)
#expect(group.coordinates[1].v == 0.5)
}

@Test func `an alternative can be built and handed to an object`() {
let uuid = UUID()
let alternative = Alternative(objectID: 7, uuid: uuid, modelResolution: .low)
let object = Object(
id: 1,
alternatives: [alternative],
content: .mesh(Mesh(vertices: [], triangles: []))
)

#expect(object.alternatives.count == 1)
#expect(object.alternatives[0].objectID == 7)
#expect(object.alternatives[0].uuid == uuid)
#expect(object.alternatives[0].modelResolution == .low)
}
}