Skip to content

Commit 5bf0bba

Browse files
committed
Update for 1.9.0 release
1 parent 3b3cefa commit 5bf0bba

File tree

127 files changed

+9958
-118
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

127 files changed

+9958
-118
lines changed

.swiftformat

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@
2929
--enable preferFinalClasses
3030
--enable redundantAsync
3131
--enable redundantThrows
32-
--enable redundantProperty
3332
--enable redundantEquatable
3433
--enable redundantMemberwiseInit
3534
--enable redundantProperty
36-

CHANGELOG.md

100755100644
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,34 @@
11
# Change Log
22

3+
## [1.9.0](https://github.com/nicklockwood/ShapeScript/releases/tag/1.9.0) (2025-12-06)
4+
5+
- Added `minkowski` command
6+
- Custom blocks now accept child objects via implicit `children` parameter
7+
- Added support for OFF (Object File Format) files
8+
- Added ShapeScript document version compatibility check
9+
- Added `Keep in Front` option to pin document window in front of other apps on macOS
10+
- Added `fonts` property for listing available fonts
11+
- Made `font` command name matching more lenient
12+
- Added undo/redo buttons to iOS source editor
13+
- Improved vertex color blending for `hull` and `extrusion` commands
14+
- Significantly improved performance when extruding complex paths such as text
15+
- The `sum` function now works with vector inputs
16+
- The `smoothing` command now works with imported meshes
17+
- Fixed some bugs with transform commands and properties in custom blocks
18+
- Fixed crash when a function has duplicate parameter names
19+
- Accessing mesh and polygon member properties now works for tuples of meshes/polygons
20+
- Fixed cache invalidation bug when using `smoothing` command
21+
- Fixed cache invalidation bug with `hull` command
22+
- Fixed error range assertion after updating document
23+
- Improved error messaging for mismatched angle types
24+
- Improved function parameter type inference
25+
- Improved type error messages
26+
- Bumped Euclid to version 0.8.11
27+
- Bumped SVGPath to version 1.2.0
28+
- Bumped LRUCache to version 1.1.2
29+
- Increased minimum iOS version to 14 and macOS version to 10.15
30+
- Increased minimum Swift version to 5.7
31+
332
## [1.8.12](https://github.com/nicklockwood/ShapeScript/releases/tag/1.8.12) (2025-08-01)
433

534
- Significantly improved performance when rendering geometry with high detail

Package.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ let package = Package(
3131
name: "ShapeScript",
3232
dependencies: ["Euclid", "LRUCache", "SVGPath"],
3333
path: "ShapeScript",
34-
exclude: ["Info.plist", "ShapeScript.xctestplan"]
34+
exclude: ["ShapeScript.xctestplan"]
3535
),
3636
.executableTarget(
3737
name: "CLI",
@@ -41,7 +41,8 @@ let package = Package(
4141
.testTarget(
4242
name: "ShapeScriptTests",
4343
dependencies: ["ShapeScript"],
44-
path: "ShapeScriptTests"
44+
path: "ShapeScriptTests",
45+
exclude: ["TestShapes/", "Stars1.jpg", "EdgeOfTheGalaxyRegular-OVEa6.otf"]
4546
),
4647
]
4748
)

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,19 @@ ShapeScript is packaged as a Swift framework, which itself depends on the [Eucli
3434
To install the ShapeScript framework using CocoaPods, add the following to your Podfile:
3535

3636
```ruby
37-
pod 'ShapeScript', '~> 1.8'
37+
pod 'ShapeScript', '~> 1.9.0'
3838
```
3939

4040
To install using Carthage, add this to your Cartfile:
4141

4242
```ogdl
43-
github "nicklockwood/ShapeScript" ~> 1.8
43+
github "nicklockwood/ShapeScript" ~> 1.9.0
4444
```
4545

4646
To install using Swift Package Manager, add this to the `dependencies:` section in your Package.swift file:
4747

4848
```swift
49-
.package(url: "https://github.com/nicklockwood/ShapeScript.git", .upToNextMinor(from: "1.8.0")),
49+
.package(url: "https://github.com/nicklockwood/ShapeScript.git", .upToNextMinor(from: "1.9.0")),
5050
```
5151

5252
The repository also includes ShapeScript Viewer apps for iOS and macOS, a cut-down version of the full ShapeScript apps available on the [Mac](https://apps.apple.com/app/id1441135869) and [iOS](https://apps.apple.com/app/id1606439346) app stores. It is not currently possible to install or run these apps using CocoaPods, Carthage or Swift Package Manager but you can run them by opening the included Xcode project and selecting the `Viewer (Mac)` or `Viewer (iOS)` schemes. For Linux, see [usage instructions](#usage-linux) below.

ShapeScript.podspec.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ShapeScript",
3-
"version": "1.8.12",
3+
"version": "1.9.0",
44
"license": {
55
"type": "MIT",
66
"file": "LICENSE.md"
@@ -10,7 +10,7 @@
1010
"authors": "Nick Lockwood",
1111
"source": {
1212
"git": "https://github.com/nicklockwood/ShapeScript.git",
13-
"tag": "1.8.12"
13+
"tag": "1.9.0"
1414
},
1515
"source_files": ["ShapeScript", "LRUCache/Sources", "SVGPath/Sources"],
1616
"requires_arc": true,

ShapeScript.xcodeproj/project.pbxproj

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,7 +1001,7 @@
10011001
"$(inherited)",
10021002
"@executable_path/Frameworks",
10031003
);
1004-
MARKETING_VERSION = 1.8.12;
1004+
MARKETING_VERSION = 1.9.0;
10051005
PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.ShapeScriptViewer;
10061006
PRODUCT_MODULE_NAME = Viewer;
10071007
PRODUCT_NAME = ShapeScript;
@@ -1039,7 +1039,7 @@
10391039
"$(inherited)",
10401040
"@executable_path/Frameworks",
10411041
);
1042-
MARKETING_VERSION = 1.8.12;
1042+
MARKETING_VERSION = 1.9.0;
10431043
PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.ShapeScriptViewer;
10441044
PRODUCT_MODULE_NAME = Viewer;
10451045
PRODUCT_NAME = ShapeScript;
@@ -1248,7 +1248,7 @@
12481248
"@executable_path/../Frameworks",
12491249
"@loader_path/Frameworks",
12501250
);
1251-
MARKETING_VERSION = 1.8.12;
1251+
MARKETING_VERSION = 1.9.0;
12521252
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
12531253
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
12541254
PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.ShapeScriptLib;
@@ -1292,7 +1292,7 @@
12921292
"@executable_path/../Frameworks",
12931293
"@loader_path/Frameworks",
12941294
);
1295-
MARKETING_VERSION = 1.8.12;
1295+
MARKETING_VERSION = 1.9.0;
12961296
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
12971297
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
12981298
PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.ShapeScriptLib;
@@ -1387,7 +1387,7 @@
13871387
"$(inherited)",
13881388
"@executable_path/../Frameworks",
13891389
);
1390-
MARKETING_VERSION = 1.8.12;
1390+
MARKETING_VERSION = 1.9.0;
13911391
PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.ShapeScriptViewer;
13921392
PRODUCT_MODULE_NAME = Viewer;
13931393
PRODUCT_NAME = "ShapeScript Viewer";
@@ -1434,7 +1434,7 @@
14341434
"$(inherited)",
14351435
"@executable_path/../Frameworks",
14361436
);
1437-
MARKETING_VERSION = 1.8.12;
1437+
MARKETING_VERSION = 1.9.0;
14381438
PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.ShapeScriptViewer;
14391439
PRODUCT_MODULE_NAME = Viewer;
14401440
PRODUCT_NAME = "ShapeScript Viewer";

ShapeScript/Geometry.swift

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -264,11 +264,11 @@ public final class Geometry: Hashable {
264264
case var .path(path):
265265
(path, material) = path.vertexColorsToMaterial(material: material)
266266
type = .path(path)
267-
case let .mesh(mesh):
268-
material = mesh.polygons.first?.material as? Material ?? material
267+
case let .mesh(mesh) where !mesh.isEmpty:
268+
material = mesh.materials.first as? Material ?? .default
269269
case .hull, .minkowski:
270270
useMaterialForCache = true
271-
case .union, .xor, .difference, .intersection, .stencil:
271+
case .union, .xor, .difference, .intersection, .stencil, .mesh:
272272
material = children.first?.material ?? .default
273273
case .group:
274274
if debug {
@@ -447,22 +447,30 @@ public extension Geometry {
447447
)
448448
}
449449

450-
/// Builds the meshes for the receiver and all its children
450+
/// Builds the meshes for the receiver and all its descendents
451451
/// Built meshes will be stored in the cache. Already-cached meshes will be re-used if available
452452
/// - Returns: false if cancelled or true when completed
453453
func build(_ callback: @escaping LegacyCallback) -> Bool {
454454
buildLeaves(callback) && buildPreview(callback) && buildFinal(callback)
455455
}
456456

457-
/// Returns the union mesh of the receiver and all its children
457+
/// Returns the union mesh of the receiver and all its descendents
458+
/// The cache is neither checked nor updated. Only already-built meshes are returned.
458459
/// - Note: Includes both material and transform
459460
func flattened(_ callback: @escaping LegacyCallback = { true }) -> Mesh {
460461
flattened(with: material, callback)
461462
}
462463

463-
/// Returns the combined mesh of the receiver and all its children
464+
/// Returns the meshes of the receiver and all its descendents
465+
/// The cache is neither checked nor updated. Only already-built meshes are returned
466+
/// - Note: Includes both material and transform
467+
func meshes(_ callback: @escaping LegacyCallback = { true }) -> [Mesh] {
468+
meshes(with: material, callback)
469+
}
470+
471+
/// Returns the combined mesh of the receiver and all its descendents
464472
/// The cache is neither checked nor updated. Only already-built meshes are returned
465-
/// - Note: Result is does *not* include the material or transform for the receiver
473+
/// - Note: Includes both material and transform
466474
func merged(_ callback: @escaping LegacyCallback = { true }) -> Mesh {
467475
var result = mesh ?? .empty
468476
if type.isLeafGeometry {
@@ -501,7 +509,7 @@ private extension Collection<Geometry> {
501509

502510
/// Returns a merged mesh for all of the geometries in the collection and all their descendents
503511
/// The cache is neither checked nor updated. Only already-built meshes are returned
504-
/// - Note: Does not include the material or transform of the top-level geometries in the collection
512+
/// - Note: Results include both material and transform
505513
func merged(_ callback: @escaping LegacyCallback) -> Mesh {
506514
var result = Mesh.empty
507515
for child in self where callback() {
@@ -514,28 +522,28 @@ private extension Collection<Geometry> {
514522
private extension Geometry {
515523
/// Computes the union of the meshes of the descendents of the receiver
516524
/// The cache is neither checked nor updated. Only already-built meshes are included in the union result
517-
/// - Note: Includes both material (if specified) and transform
525+
/// - Note: Includes the receiver's material but not its transform
518526
func flattenedChildren(_ callback: @escaping LegacyCallback) -> [Mesh] {
519527
children.flattened(with: material, callback)
520528
}
521529

522530
/// Returns a merged mesh for all of the descendents of the receiver
523531
/// The cache is neither checked nor updated. Only already-built meshes are returned
524-
/// - Note: Does not include the material or transform of the top-level geometries in the collection
532+
/// - Note: Does not include the material or transform of the receiver
525533
func mergedChildren(_ callback: @escaping LegacyCallback) -> Mesh {
526534
children.merged(callback)
527535
}
528536

529537
/// Computes the union of the meshes of the first child of the receiver
530538
/// The cache is neither checked nor updated. Only already-built meshes are included in the union result
531-
/// - Note: Includes both material (if specified) and transform
539+
/// - Note: Includes the receiver's material but not its transform
532540
func flattenedFirstChild(_ callback: @escaping LegacyCallback) -> Mesh {
533541
children.first.map { $0.flattened(with: self.material, callback) } ?? .empty
534542
}
535543

536-
/// Returns the meshes of the receiver and all its descendents
544+
/// Returns the meshes of the receivers children and their descendents
537545
/// The cache is neither checked nor updated. Only already-built meshes are returned
538-
/// - Note: Includes both the material and transform
546+
/// - Note: Includes the receiver's material but not its transform
539547
func childMeshes(_ callback: @escaping () -> Bool) -> [Mesh] {
540548
children.meshes(with: material, callback)
541549
}
@@ -547,28 +555,24 @@ private extension Geometry {
547555
.union(meshes(with: material, callback), isCancelled: { !callback() })
548556
}
549557

550-
/// Returns the meshes of the receiver and all its children
551-
/// The cache is neither checked nor updated. Only already-built meshes are returned.
558+
/// Returns the meshes of the receiver and all its descendents
559+
/// The cache is neither checked nor updated. Only already-built meshes are returned
552560
/// - Note: Includes both material (if specified) and transform
553561
func meshes(with material: Material?, _ callback: @escaping LegacyCallback) -> [Mesh] {
554562
var meshes = [Mesh]()
555-
if var mesh, mesh != .empty {
556-
mesh = mesh.transformed(by: transform)
557-
if material != self.material {
558-
mesh = mesh.replacing(nil, with: self.material)
559-
}
563+
if let mesh, mesh != .empty {
560564
meshes.append(mesh)
561565
}
562566
if type.isLeafGeometry {
563-
meshes += childMeshes(callback).map {
564-
let mesh = $0.transformed(by: transform)
565-
if material != self.material {
566-
return mesh.replacing(nil, with: self.material)
567-
}
568-
return mesh
567+
meshes += childMeshes(callback)
568+
}
569+
return meshes.map {
570+
let mesh = $0.transformed(by: transform)
571+
if material != self.material {
572+
return mesh.replacing(nil, with: self.material)
569573
}
574+
return mesh
570575
}
571-
return meshes
572576
}
573577

574578
/// Build all geometries that don't have dependencies
@@ -1056,13 +1060,7 @@ public extension Geometry {
10561060
/// Builds the mesh (if needed) and returns the triangle count
10571061
/// Built meshes will be stored in the cache. Already-cached meshes will be re-used if available
10581062
func triangles(_ isCancelled: @escaping CancellationHandler) -> [Polygon] {
1059-
switch type {
1060-
case .group:
1061-
return children.reduce(into: []) { $0 += $1.triangles(isCancelled) }
1062-
default:
1063-
_ = build { !isCancelled() }
1064-
return mesh?.triangulate().polygons ?? []
1065-
}
1063+
polygons(isCancelled).flatMap { $0.triangulate() }
10661064
}
10671065

10681066
/// Returns if the geometry is watertight
@@ -1072,7 +1070,7 @@ public extension Geometry {
10721070
case .cone, .cylinder, .sphere, .cube:
10731071
return true
10741072
case .group:
1075-
return children.reduce(true) { $0 && $1.isWatertight(isCancelled) }
1073+
return children.allSatisfy { $0.isWatertight(isCancelled) }
10761074
default:
10771075
_ = build { !isCancelled() }
10781076
return mesh?.isWatertight ?? true

ShapeScript/Interpreter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import Foundation
1111

1212
// MARK: Public interface
1313

14-
public let version: String = "1.8.12"
14+
public let version: String = "1.9.0"
1515

1616
public func evaluate(
1717
_ program: Program,

ShapeScript/Values.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ extension Value {
185185
case let .font(font): return font
186186
case let .text(text): return text
187187
case let .path(path): return path
188-
case let .mesh(mesh): return mesh
188+
case let .mesh(geometry): return geometry
189189
case let .polygon(polygon): return polygon
190190
case let .point(point): return point
191191
case let .tuple(values): return values.map(\.value)

ShapeScriptTests/MetadataTests.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ private let projectDirectory: URL = testsDirectory.deletingLastPathComponent()
1414
private let changelogURL = projectDirectory
1515
.appendingPathComponent("CHANGELOG.md")
1616

17+
private let readmeURL = projectDirectory
18+
.appendingPathComponent("README.md")
19+
1720
private let whatsNewMacURL = projectDirectory
1821
.appendingPathComponent("Viewer/Mac/WhatsNew.rtf")
1922

@@ -176,6 +179,14 @@ final class MetadataTests: XCTestCase {
176179
)
177180
}
178181

182+
func testLatestVersionInReadme() throws {
183+
let readme = try String(contentsOf: readmeURL, encoding: .utf8)
184+
XCTAssertTrue(
185+
readme.contains("from: \"\(projectVersion)\""),
186+
"README.md version does not match latest release"
187+
)
188+
}
189+
179190
func testLatestVersionInPodspec() throws {
180191
let podspec = try String(contentsOf: podspecURL, encoding: .utf8)
181192
XCTAssertTrue(

0 commit comments

Comments
 (0)