kupcad 0.1.10-x86_64-linux-gnu

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 (29) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +661 -0
  3. data/README.md +98 -0
  4. data/lib/kupcad/native/THIRD_PARTY_NOTICES.md +29 -0
  5. data/lib/kupcad/native/libkupcad.so +0 -0
  6. data/lib/kupcad/native/licenses/AllertaStencil-OFL.txt +93 -0
  7. data/lib/kupcad/native/licenses/NotoSymbols-OFL.txt +98 -0
  8. data/lib/kupcad/native/licenses/RobotoSerif-OFL.txt +93 -0
  9. data/lib/kupcad/native/licenses/draco/LICENSE +252 -0
  10. data/lib/kupcad/native/licenses/eigen/LICENSE +373 -0
  11. data/lib/kupcad/native/licenses/eigen/LICENSES/Apache-2.0.txt +203 -0
  12. data/lib/kupcad/native/licenses/eigen/LICENSES/BSD-3-Clause-Open-MPI.txt +34 -0
  13. data/lib/kupcad/native/licenses/eigen/LICENSES/BSD-3-Clause.txt +26 -0
  14. data/lib/kupcad/native/licenses/eigen/LICENSES/LicenseRef-MINPACK.txt +51 -0
  15. data/lib/kupcad/native/licenses/eigen/LICENSES/MPL-2.0.txt +373 -0
  16. data/lib/kupcad/native/licenses/harfbuzz/COPYING +42 -0
  17. data/lib/kupcad/native/licenses/harfbuzz/ms-use/COPYING +21 -0
  18. data/lib/kupcad/native/licenses/lsp-kit-MIT.txt +21 -0
  19. data/lib/kupcad/native/licenses/manifold/LICENSE +201 -0
  20. data/lib/kupcad/native/licenses/onetbb/LICENSE.txt +201 -0
  21. data/lib/kupcad/native/licenses/onetbb/third-party-programs.txt +198 -0
  22. data/lib/kupcad/native/licenses/sheenbidi/LICENSE +202 -0
  23. data/lib/kupcad/native/licenses/tatfi/LICENSE +373 -0
  24. data/lib/kupcad/native/licenses/zig-MIT.txt +21 -0
  25. data/lib/kupcad/native.rb +77 -0
  26. data/lib/kupcad/version.rb +6 -0
  27. data/lib/kupcad.rb +199 -0
  28. data/sig/kupcad.rbs +61 -0
  29. metadata +90 -0
data/lib/kupcad.rb ADDED
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require_relative 'kupcad/version'
5
+ require_relative 'kupcad/native'
6
+
7
+ # KupCAD from Ruby: build the model of a KupCAD script, with its parameters, in any format the
8
+ # CLI exports, and format, lint and read the parameters of scripts. It runs libkupcad, the
9
+ # KupCAD core as a C library, in the process; a script reads only the files given with it.
10
+ #
11
+ # model = Kupcad.build(File.read('bracket.kup'), format: :stl, params: { width: 60 })
12
+ # File.binwrite('bracket.stl', model.data)
13
+ module Kupcad
14
+ # What every error of the gem's is: raised itself when libkupcad fails on its own (a bug of
15
+ # the core's)
16
+ class Error < StandardError; end
17
+
18
+ # The script doesn't parse or compile, raises (a value one of its parameters refuses among
19
+ # it: "ValueError: ..."), runs past its time or memory limit, or leaves no model to export.
20
+ # The message says where and why: "2:5: Invalid expression ...", "RuntimeError: Plate too
21
+ # thin".
22
+ class ScriptError < Error
23
+ # What the script printed and warned of before it failed (a Report); nil when nothing ran
24
+ # (Kupcad.format's, check's and params')
25
+ attr_reader :report
26
+
27
+ def initialize(message, report = nil)
28
+ super(message)
29
+ @report = report
30
+ end
31
+ end
32
+
33
+ # Something the script warned of, at its line and column (1-based; nil where it wasn't
34
+ # running, as the model was meshed), in `file`, the imported file whose code warned (nil for
35
+ # the script's own)
36
+ RunWarning = Data.define(:message, :line, :column, :file)
37
+
38
+ # What a run printed (`puts`, `print`, `warn`), up to 64 KB (`output_cut` past them), and
39
+ # what it warned of
40
+ Report = Data.define(:output, :output_cut, :warnings) do
41
+ def self.from_json(json)
42
+ fields = JSON.parse(json, symbolize_names: true)
43
+ warnings = fields[:warnings].map do |warning|
44
+ RunWarning.new(message: warning[:message], line: warning[:line], column: warning[:column], file: warning[:file])
45
+ end
46
+ new(output: fields[:output], output_cut: fields[:outputCut], warnings:)
47
+ end
48
+ end
49
+
50
+ # A built model: its bytes in `format` (binary), and the run's report
51
+ Model = Data.define(:data, :format, :report)
52
+
53
+ # A lint finding or a syntax error, as `kupcad check --format json` gives it: its severity
54
+ # ("error", "warning" or "info"), its rule (nil for a syntax error), its code, its message,
55
+ # where it starts and ends (1-based), and its fix (a Hash, or nil)
56
+ Finding = Data.define(:severity, :rule, :code, :message, :line, :column, :end_line, :end_column, :fix)
57
+
58
+ # The most a limit, or a file's length, can be as libkupcad takes it
59
+ UINT64_MAX = (2**64) - 1
60
+ UINT32_MAX = (2**32) - 1
61
+
62
+ # The encodings a String is passed in as it is: UTF-8, and those File.binread and File.read in
63
+ # a C locale tag whatever bytes they read with
64
+ RAW_ENCODINGS = [Encoding::UTF_8, Encoding::BINARY, Encoding::US_ASCII].freeze
65
+ private_constant :UINT64_MAX, :UINT32_MAX, :RAW_ENCODINGS
66
+
67
+ class << self
68
+ # The version of the libkupcad loaded: the gem's own, unless KUPCAD_LIBRARY names another
69
+ def library_version
70
+ library_info[:version]
71
+ end
72
+
73
+ # `source` formatted, as `kupcad fmt` formats it. Raises ScriptError when it doesn't parse.
74
+ def format(source)
75
+ text(:kupcad_format, source)
76
+ end
77
+
78
+ # The findings of `source`: lint findings and syntax errors, an Array of Finding
79
+ def check(source)
80
+ JSON.parse(text(:kupcad_check, source), symbolize_names: true).map do |finding|
81
+ Finding.new(severity: finding[:severity], rule: finding[:rule], code: finding[:code],
82
+ message: finding[:message], line: finding[:line], column: finding[:column],
83
+ end_line: finding[:endLine], end_column: finding[:endColumn], fix: finding[:fix])
84
+ end
85
+ end
86
+
87
+ # The parameters `source` declares and its metadata, the schema `kupcad doc` prints:
88
+ # { meta: { title: ... }, parameters: [{ name: ..., type: ..., default: ... }] }
89
+ def params(source)
90
+ JSON.parse(text(:kupcad_params, source), symbolize_names: true)
91
+ end
92
+
93
+ # Runs `source` and builds its model in `format` (one of FORMATS), a Model.
94
+ #
95
+ # params: the parameters' values by name, as `kupcad build -p` takes them
96
+ # ({ width: 60, label: 'A', holes: [[5, 5], [35, 5]] }).
97
+ # files: the files the script imports, by their path from its folder, text or binary
98
+ # ({ 'lib/gears.kup' => File.read('lib/gears.kup'), 'parts/base.stl' => File.binread(...) }).
99
+ # memory_limit: the most memory, in bytes, the script may hold; nil for no limit.
100
+ # timeout: the most time, in seconds, the build may take; nil (or Float::INFINITY) for no
101
+ # limit. The time is checked every few thousand instructions and between geometry
102
+ # operations.
103
+ # draco: compress a GLB's or a glTF's meshes with Draco.
104
+ #
105
+ # Raises ScriptError when the script fails, with what it printed and warned of until then,
106
+ # ArgumentError for a format that isn't one, a limit that isn't positive, or parameter
107
+ # values JSON can't hold, and TypeError for a source or a file's content that isn't a String.
108
+ def build(source, format: :stl, params: {}, files: {}, memory_limit: nil, timeout: nil, draco: false)
109
+ format = format.to_sym
110
+ unless FORMATS.include?(format)
111
+ raise ArgumentError, "#{format.inspect} isn't a format: one of #{FORMATS.join(', ')}"
112
+ end
113
+
114
+ data, report = call(:kupcad_build, *Native.buffer(utf8(source, 'source')), format.to_s,
115
+ *Native.buffer(params_json(params)), *Native.buffer(pack(files)),
116
+ limit(memory_limit, 'memory_limit'), milliseconds(timeout), draco ? true : false)
117
+ Model.new(data:, format:, report: Report.from_json(report))
118
+ end
119
+
120
+ private
121
+
122
+ # What the library says of itself: { version:, protocolVersion:, os:, arch:, formats: }
123
+ def library_info
124
+ JSON.parse(Native.utf8(call(:kupcad_version).first), symbolize_names: true)
125
+ end
126
+
127
+ # The data of libkupcad's `function` for `source`, as UTF-8 text; raises what failed
128
+ def text(function, source)
129
+ Native.utf8(call(function, *Native.buffer(utf8(source, 'source'))).first)
130
+ end
131
+
132
+ # [data, report] of libkupcad's `function` called with `arguments`, the data binary; raises
133
+ # what failed. An interrupt that comes while it runs (Timeout, Ctrl-C, Thread#raise) is
134
+ # raised once the result is read and freed: raised as the call returned, it lost the result.
135
+ def call(function, *arguments)
136
+ status, data, message, report = Thread.handle_interrupt(Object => :never) do
137
+ Native.take(Native.public_send(function, *arguments))
138
+ end
139
+ case status
140
+ when :ok then [data, report]
141
+ when :script_error then raise ScriptError.new(message, report && Report.from_json(report))
142
+ when :invalid_argument then raise ArgumentError, message
143
+ when :out_of_memory then raise NoMemoryError, message
144
+ else raise Error, message
145
+ end
146
+ end
147
+
148
+ # `text` as libkupcad reads it, UTF-8: transcoded from the encoding it says it is in
149
+ def utf8(text, name)
150
+ raise TypeError, "#{name} is #{text.class}: a String" unless text.is_a?(String)
151
+
152
+ RAW_ENCODINGS.include?(text.encoding) ? text : text.encode(Encoding::UTF_8)
153
+ end
154
+
155
+ def params_json(params)
156
+ params.empty? ? '' : JSON.generate(params)
157
+ rescue JSON::GeneratorError, JSON::NestingError => e
158
+ raise ArgumentError, "parameters: #{e.message}"
159
+ end
160
+
161
+ # A limit as the library takes it, 0 for none; one past what it holds is none either
162
+ def limit(value, name)
163
+ return 0 if value.nil?
164
+ unless value.is_a?(Integer) && value.positive?
165
+ raise ArgumentError, "#{name} is #{value.inspect}: a positive Integer, or nil for none"
166
+ end
167
+
168
+ [value, UINT64_MAX].min
169
+ end
170
+
171
+ # Seconds as the library's whole milliseconds, at least 1; 0 for no limit
172
+ def milliseconds(seconds)
173
+ return 0 if seconds.nil?
174
+ unless seconds.is_a?(Numeric) && seconds.real? && seconds.positive?
175
+ raise ArgumentError, "timeout is #{seconds.inspect}: positive seconds, or nil for none"
176
+ end
177
+
178
+ milliseconds = seconds * 1000
179
+ milliseconds >= UINT64_MAX ? UINT64_MAX : [milliseconds.round, 1].max
180
+ end
181
+
182
+ # The files packed as kupcad.h describes: each its path's length, its path (UTF-8), its
183
+ # content's length and its content (its bytes), each length 4 bytes, little-endian
184
+ def pack(files)
185
+ files.each_with_object(String.new(encoding: Encoding::BINARY)) do |(path, content), packed|
186
+ raise TypeError, "files: #{path} is #{content.class}, not its content as a String" unless content.is_a?(String)
187
+
188
+ [utf8(path.to_s, 'a path'), content].each do |field|
189
+ raise ArgumentError, "files: #{path} is past 4 GiB, the most a file holds" if field.bytesize > UINT32_MAX
190
+
191
+ packed << [field.bytesize].pack('V') << field.b
192
+ end
193
+ end
194
+ end
195
+ end
196
+
197
+ # The formats a model can be built in, as the library loaded names them
198
+ FORMATS = library_info[:formats].map(&:to_sym).freeze
199
+ end
data/sig/kupcad.rbs ADDED
@@ -0,0 +1,61 @@
1
+ module Kupcad
2
+ VERSION: String
3
+ FORMATS: Array[Symbol]
4
+
5
+ class Error < StandardError
6
+ end
7
+
8
+ class ScriptError < Error
9
+ attr_reader report: Report?
10
+
11
+ def initialize: (String message, ?Report? report) -> void
12
+ end
13
+
14
+ class RunWarning < Data
15
+ attr_reader message: String
16
+ attr_reader line: Integer?
17
+ attr_reader column: Integer?
18
+ attr_reader file: String?
19
+
20
+ def self.new: (message: String, line: Integer?, column: Integer?, file: String?) -> RunWarning
21
+ end
22
+
23
+ class Report < Data
24
+ attr_reader output: String
25
+ attr_reader output_cut: bool
26
+ attr_reader warnings: Array[RunWarning]
27
+
28
+ def self.new: (output: String, output_cut: bool, warnings: Array[RunWarning]) -> Report
29
+ def self.from_json: (String json) -> Report
30
+ end
31
+
32
+ class Model < Data
33
+ attr_reader data: String
34
+ attr_reader format: Symbol
35
+ attr_reader report: Report
36
+
37
+ def self.new: (data: String, format: Symbol, report: Report) -> Model
38
+ end
39
+
40
+ class Finding < Data
41
+ attr_reader severity: String
42
+ attr_reader rule: String?
43
+ attr_reader code: String?
44
+ attr_reader message: String
45
+ attr_reader line: Integer
46
+ attr_reader column: Integer
47
+ attr_reader end_line: Integer
48
+ attr_reader end_column: Integer
49
+ attr_reader fix: Hash[Symbol, untyped]?
50
+
51
+ def self.new: (severity: String, rule: String?, code: String?, message: String, line: Integer, column: Integer,
52
+ end_line: Integer, end_column: Integer, fix: Hash[Symbol, untyped]?) -> Finding
53
+ end
54
+
55
+ def self.library_version: () -> String
56
+ def self.format: (String source) -> String
57
+ def self.check: (String source) -> Array[Finding]
58
+ def self.params: (String source) -> Hash[Symbol, untyped]
59
+ def self.build: (String source, ?format: Symbol | String, ?params: Hash[Symbol | String, untyped],
60
+ ?files: Hash[_ToS, String], ?memory_limit: Integer?, ?timeout: Numeric?, ?draco: boolish) -> Model
61
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kupcad
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.10
5
+ platform: x86_64-linux-gnu
6
+ authors:
7
+ - Oleksii Vasyliev
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: ffi
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.15'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.15'
26
+ description: Builds the model of a KupCAD script, with its parameters, as STL, 3MF,
27
+ glTF, OBJ, STEP, SVG or DXF, and formats, lints and reads the parameters of scripts,
28
+ in process, through libkupcad, the KupCAD core as a C library, which each platform
29
+ gem carries.
30
+ email:
31
+ - le0pard@users.noreply.github.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - LICENSE
37
+ - README.md
38
+ - lib/kupcad.rb
39
+ - lib/kupcad/native.rb
40
+ - lib/kupcad/native/THIRD_PARTY_NOTICES.md
41
+ - lib/kupcad/native/libkupcad.so
42
+ - lib/kupcad/native/licenses/AllertaStencil-OFL.txt
43
+ - lib/kupcad/native/licenses/NotoSymbols-OFL.txt
44
+ - lib/kupcad/native/licenses/RobotoSerif-OFL.txt
45
+ - lib/kupcad/native/licenses/draco/LICENSE
46
+ - lib/kupcad/native/licenses/eigen/LICENSE
47
+ - lib/kupcad/native/licenses/eigen/LICENSES/Apache-2.0.txt
48
+ - lib/kupcad/native/licenses/eigen/LICENSES/BSD-3-Clause-Open-MPI.txt
49
+ - lib/kupcad/native/licenses/eigen/LICENSES/BSD-3-Clause.txt
50
+ - lib/kupcad/native/licenses/eigen/LICENSES/LicenseRef-MINPACK.txt
51
+ - lib/kupcad/native/licenses/eigen/LICENSES/MPL-2.0.txt
52
+ - lib/kupcad/native/licenses/harfbuzz/COPYING
53
+ - lib/kupcad/native/licenses/harfbuzz/ms-use/COPYING
54
+ - lib/kupcad/native/licenses/lsp-kit-MIT.txt
55
+ - lib/kupcad/native/licenses/manifold/LICENSE
56
+ - lib/kupcad/native/licenses/onetbb/LICENSE.txt
57
+ - lib/kupcad/native/licenses/onetbb/third-party-programs.txt
58
+ - lib/kupcad/native/licenses/sheenbidi/LICENSE
59
+ - lib/kupcad/native/licenses/tatfi/LICENSE
60
+ - lib/kupcad/native/licenses/zig-MIT.txt
61
+ - lib/kupcad/version.rb
62
+ - sig/kupcad.rbs
63
+ homepage: https://kupcad.org
64
+ licenses:
65
+ - AGPL-3.0-only
66
+ metadata:
67
+ homepage_uri: https://kupcad.org
68
+ source_code_uri: https://github.com/kupcad/kupcad/tree/main/bindings/ruby
69
+ documentation_uri: https://docs.kupcad.org
70
+ changelog_uri: https://github.com/kupcad/kupcad/releases
71
+ bug_tracker_uri: https://github.com/kupcad/kupcad/issues
72
+ rubygems_mfa_required: 'true'
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '3.2'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubygems_version: 4.0.20
88
+ specification_version: 4
89
+ summary: 'KupCAD for Ruby: build parametric CAD models from KupCAD scripts'
90
+ test_files: []