ruby3mf 0.2.6 → 0.2.7
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.
- checksums.yaml +4 -4
- data/.gitignore +13 -13
- data/.rspec +2 -2
- data/bin/cli.rb +12 -12
- data/bin/console +14 -14
- data/bin/folder_test.sh +13 -13
- data/bin/suite_test.sh +41 -41
- data/lib/ruby3mf/3MFcoreSpec_1.1.xsd.template +188 -188
- data/lib/ruby3mf/content_types.rb +79 -77
- data/lib/ruby3mf/document.rb +242 -242
- data/lib/ruby3mf/edge_list.rb +60 -60
- data/lib/ruby3mf/errors.yml +191 -188
- data/lib/ruby3mf/log3mf.rb +135 -135
- data/lib/ruby3mf/mesh_analyzer.rb +80 -80
- data/lib/ruby3mf/mesh_normal_analyzer.rb +218 -218
- data/lib/ruby3mf/model3mf.rb +120 -117
- data/lib/ruby3mf/relationships.rb +50 -50
- data/lib/ruby3mf/schema_files.rb +26 -26
- data/lib/ruby3mf/texture3mf.rb +29 -29
- data/lib/ruby3mf/thumbnail3mf.rb +21 -21
- data/lib/ruby3mf/version.rb +3 -3
- data/lib/ruby3mf/xml.xsd +286 -286
- data/lib/ruby3mf/xml_val.rb +68 -68
- data/lib/ruby3mf.rb +26 -26
- data/ruby3mf.gemspec +32 -32
- metadata +3 -4
- data/suite.011917.out +0 -237
data/lib/ruby3mf/log3mf.rb
CHANGED
@@ -1,135 +1,135 @@
|
|
1
|
-
require 'singleton'
|
2
|
-
require 'yaml'
|
3
|
-
|
4
|
-
# Example usage:
|
5
|
-
|
6
|
-
# Log3mf.context "box.3mf" do |l|
|
7
|
-
# --do some stuff here
|
8
|
-
|
9
|
-
# l.context "[Content-Types].xml" do |l|
|
10
|
-
# -- try to parse file. if fail...
|
11
|
-
# l.log(:fatal_error, "couldn't parse XML") <<<--- THIS WILL GENERATE FATAL ERROR EXCEPTION
|
12
|
-
# end
|
13
|
-
|
14
|
-
# l.context "examing Relations" do |l|
|
15
|
-
# l.log(:error, "a non-fatal error")
|
16
|
-
# l.log(:warning, "a warning")
|
17
|
-
# l.log(:info, "it is warm today")
|
18
|
-
# end
|
19
|
-
# end
|
20
|
-
#
|
21
|
-
# Log3mf.to_json
|
22
|
-
|
23
|
-
|
24
|
-
class Log3mf
|
25
|
-
include Singleton
|
26
|
-
include Interpolation
|
27
|
-
|
28
|
-
LOG_LEVELS = [:fatal_error, :error, :warning, :info, :debug]
|
29
|
-
|
30
|
-
SPEC_LINKS = {
|
31
|
-
core: 'http://3mf.io/wp-content/uploads/2016/03/3MFcoreSpec_1.1.pdf',
|
32
|
-
material: 'http://3mf.io/wp-content/uploads/2015/04/3MFmaterialsSpec_1.0.1.pdf',
|
33
|
-
production: 'http://3mf.io/wp-content/uploads/2016/07/3MFproductionSpec.pdf',
|
34
|
-
slice: 'http://3mf.io/wp-content/uploads/2016/07/3MFsliceSpec.pdf',
|
35
|
-
#opc: 'http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-376,%20Fourth%20Edition,%20Part%202%20-%20Open%20Packaging%20Conventions.zip'
|
36
|
-
opc: 'http://3mf.io/wp-content/uploads/2016/03/3MFcoreSpec_1.1.pdf'
|
37
|
-
}.freeze
|
38
|
-
|
39
|
-
# Allows us to throw FatalErrors if we ever get errors of severity :fatal_error
|
40
|
-
class FatalError < RuntimeError
|
41
|
-
end
|
42
|
-
|
43
|
-
def initialize
|
44
|
-
@log_list = []
|
45
|
-
@context_stack = []
|
46
|
-
@ledger = []
|
47
|
-
errormap_path = File.join(File.dirname(__FILE__), "errors.yml")
|
48
|
-
@errormap = YAML.load_file(errormap_path)
|
49
|
-
end
|
50
|
-
|
51
|
-
def reset_log
|
52
|
-
@log_list = []
|
53
|
-
@context_stack = []
|
54
|
-
end
|
55
|
-
|
56
|
-
def self.reset_log
|
57
|
-
Log3mf.instance.reset_log
|
58
|
-
end
|
59
|
-
|
60
|
-
def context (context_description, &block)
|
61
|
-
@context_stack.push(context_description)
|
62
|
-
retval = block.call(Log3mf.instance)
|
63
|
-
@context_stack.pop
|
64
|
-
retval
|
65
|
-
end
|
66
|
-
|
67
|
-
def self.context(context_description, &block)
|
68
|
-
Log3mf.instance.context(context_description, &block)
|
69
|
-
end
|
70
|
-
|
71
|
-
def method_missing(name, *args, &block)
|
72
|
-
if LOG_LEVELS.include? name.to_sym
|
73
|
-
if [:fatal_error, :error, :debug].include? name.to_sym
|
74
|
-
linenumber = caller_locations[0].to_s.split('/')[-1].split(':')[-2].to_s
|
75
|
-
filename = caller_locations[0].to_s.split('/')[-1].split(':')[0].to_s
|
76
|
-
options = {linenumber: linenumber, filename: filename}
|
77
|
-
# Mike: do not call error or fatal_error without an entry in errors.yml
|
78
|
-
raise "{fatal_}error called WITHOUT using error symbol from: #{filename}:#{linenumber}" if ( !(args[0].is_a? Symbol) && (name.to_sym != :debug) )
|
79
|
-
|
80
|
-
puts "***** Log3mf.#{name} called from #{filename}:#{linenumber} *****" if $DEBUG
|
81
|
-
|
82
|
-
options = options.merge(args[1]) if args[1]
|
83
|
-
log(name.to_sym, args[0], options)
|
84
|
-
else
|
85
|
-
log(name.to_sym, *args)
|
86
|
-
end
|
87
|
-
else
|
88
|
-
super
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def log(severity, message, options = {})
|
93
|
-
error = @errormap.fetch(message.to_s) { {"msg" => message.to_s, "page" => nil} }
|
94
|
-
options[:page] = error["page"] unless options[:page]
|
95
|
-
options[:spec] = error["spec"] unless options[:spec]
|
96
|
-
entry = {id: message,
|
97
|
-
context: "#{@context_stack.join("/")}",
|
98
|
-
severity: severity,
|
99
|
-
message: interpolate(error["msg"], options)}
|
100
|
-
entry[:spec_ref] = spec_link(options[:spec], options[:page]) if (options && options[:page])
|
101
|
-
entry[:caller] = "#{options[:filename]}:#{options[:linenumber]}" if (options && options[:filename] && options[:linenumber])
|
102
|
-
@log_list << entry
|
103
|
-
raise FatalError if severity == :fatal_error
|
104
|
-
end
|
105
|
-
|
106
|
-
def count_entries(*levels)
|
107
|
-
entries(*levels).count
|
108
|
-
end
|
109
|
-
|
110
|
-
def self.count_entries(*l)
|
111
|
-
Log3mf.instance.count_entries(*l)
|
112
|
-
end
|
113
|
-
|
114
|
-
def entries(*levels)
|
115
|
-
return @log_list if levels.size == 0
|
116
|
-
@log_list.select { |i| levels.include? i[:severity] }
|
117
|
-
end
|
118
|
-
|
119
|
-
def self.entries(*l)
|
120
|
-
Log3mf.instance.entries(*l)
|
121
|
-
end
|
122
|
-
|
123
|
-
def spec_link(spec, page)
|
124
|
-
spec = :core unless spec
|
125
|
-
"#{SPEC_LINKS[spec]}#page=#{page}"
|
126
|
-
end
|
127
|
-
|
128
|
-
def to_json
|
129
|
-
@log_list.to_json
|
130
|
-
end
|
131
|
-
|
132
|
-
def self.to_json
|
133
|
-
Log3mf.instance.to_json
|
134
|
-
end
|
135
|
-
end
|
1
|
+
require 'singleton'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
# Example usage:
|
5
|
+
|
6
|
+
# Log3mf.context "box.3mf" do |l|
|
7
|
+
# --do some stuff here
|
8
|
+
|
9
|
+
# l.context "[Content-Types].xml" do |l|
|
10
|
+
# -- try to parse file. if fail...
|
11
|
+
# l.log(:fatal_error, "couldn't parse XML") <<<--- THIS WILL GENERATE FATAL ERROR EXCEPTION
|
12
|
+
# end
|
13
|
+
|
14
|
+
# l.context "examing Relations" do |l|
|
15
|
+
# l.log(:error, "a non-fatal error")
|
16
|
+
# l.log(:warning, "a warning")
|
17
|
+
# l.log(:info, "it is warm today")
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# Log3mf.to_json
|
22
|
+
|
23
|
+
|
24
|
+
class Log3mf
|
25
|
+
include Singleton
|
26
|
+
include Interpolation
|
27
|
+
|
28
|
+
LOG_LEVELS = [:fatal_error, :error, :warning, :info, :debug]
|
29
|
+
|
30
|
+
SPEC_LINKS = {
|
31
|
+
core: 'http://3mf.io/wp-content/uploads/2016/03/3MFcoreSpec_1.1.pdf',
|
32
|
+
material: 'http://3mf.io/wp-content/uploads/2015/04/3MFmaterialsSpec_1.0.1.pdf',
|
33
|
+
production: 'http://3mf.io/wp-content/uploads/2016/07/3MFproductionSpec.pdf',
|
34
|
+
slice: 'http://3mf.io/wp-content/uploads/2016/07/3MFsliceSpec.pdf',
|
35
|
+
#opc: 'http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-376,%20Fourth%20Edition,%20Part%202%20-%20Open%20Packaging%20Conventions.zip'
|
36
|
+
opc: 'http://3mf.io/wp-content/uploads/2016/03/3MFcoreSpec_1.1.pdf'
|
37
|
+
}.freeze
|
38
|
+
|
39
|
+
# Allows us to throw FatalErrors if we ever get errors of severity :fatal_error
|
40
|
+
class FatalError < RuntimeError
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize
|
44
|
+
@log_list = []
|
45
|
+
@context_stack = []
|
46
|
+
@ledger = []
|
47
|
+
errormap_path = File.join(File.dirname(__FILE__), "errors.yml")
|
48
|
+
@errormap = YAML.load_file(errormap_path)
|
49
|
+
end
|
50
|
+
|
51
|
+
def reset_log
|
52
|
+
@log_list = []
|
53
|
+
@context_stack = []
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.reset_log
|
57
|
+
Log3mf.instance.reset_log
|
58
|
+
end
|
59
|
+
|
60
|
+
def context (context_description, &block)
|
61
|
+
@context_stack.push(context_description)
|
62
|
+
retval = block.call(Log3mf.instance)
|
63
|
+
@context_stack.pop
|
64
|
+
retval
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.context(context_description, &block)
|
68
|
+
Log3mf.instance.context(context_description, &block)
|
69
|
+
end
|
70
|
+
|
71
|
+
def method_missing(name, *args, &block)
|
72
|
+
if LOG_LEVELS.include? name.to_sym
|
73
|
+
if [:fatal_error, :error, :debug].include? name.to_sym
|
74
|
+
linenumber = caller_locations[0].to_s.split('/')[-1].split(':')[-2].to_s
|
75
|
+
filename = caller_locations[0].to_s.split('/')[-1].split(':')[0].to_s
|
76
|
+
options = {linenumber: linenumber, filename: filename}
|
77
|
+
# Mike: do not call error or fatal_error without an entry in errors.yml
|
78
|
+
raise "{fatal_}error called WITHOUT using error symbol from: #{filename}:#{linenumber}" if ( !(args[0].is_a? Symbol) && (name.to_sym != :debug) )
|
79
|
+
|
80
|
+
puts "***** Log3mf.#{name} called from #{filename}:#{linenumber} *****" if $DEBUG
|
81
|
+
|
82
|
+
options = options.merge(args[1]) if args[1]
|
83
|
+
log(name.to_sym, args[0], options)
|
84
|
+
else
|
85
|
+
log(name.to_sym, *args)
|
86
|
+
end
|
87
|
+
else
|
88
|
+
super
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def log(severity, message, options = {})
|
93
|
+
error = @errormap.fetch(message.to_s) { {"msg" => message.to_s, "page" => nil} }
|
94
|
+
options[:page] = error["page"] unless options[:page]
|
95
|
+
options[:spec] = error["spec"] unless options[:spec]
|
96
|
+
entry = {id: message,
|
97
|
+
context: "#{@context_stack.join("/")}",
|
98
|
+
severity: severity,
|
99
|
+
message: interpolate(error["msg"], options)}
|
100
|
+
entry[:spec_ref] = spec_link(options[:spec], options[:page]) if (options && options[:page])
|
101
|
+
entry[:caller] = "#{options[:filename]}:#{options[:linenumber]}" if (options && options[:filename] && options[:linenumber])
|
102
|
+
@log_list << entry
|
103
|
+
raise FatalError if severity == :fatal_error
|
104
|
+
end
|
105
|
+
|
106
|
+
def count_entries(*levels)
|
107
|
+
entries(*levels).count
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.count_entries(*l)
|
111
|
+
Log3mf.instance.count_entries(*l)
|
112
|
+
end
|
113
|
+
|
114
|
+
def entries(*levels)
|
115
|
+
return @log_list if levels.size == 0
|
116
|
+
@log_list.select { |i| levels.include? i[:severity] }
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.entries(*l)
|
120
|
+
Log3mf.instance.entries(*l)
|
121
|
+
end
|
122
|
+
|
123
|
+
def spec_link(spec, page)
|
124
|
+
spec = :core unless spec
|
125
|
+
"#{SPEC_LINKS[spec]}#page=#{page}"
|
126
|
+
end
|
127
|
+
|
128
|
+
def to_json
|
129
|
+
@log_list.to_json
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.to_json
|
133
|
+
Log3mf.instance.to_json
|
134
|
+
end
|
135
|
+
end
|
@@ -1,80 +1,80 @@
|
|
1
|
-
class MeshAnalyzer
|
2
|
-
|
3
|
-
def self.validate_object(object, includes_material)
|
4
|
-
Log3mf.context "verifying object" do |l|
|
5
|
-
children = object.children.map { |child| child.name }
|
6
|
-
have_override = object.attributes["pid"] or object.attributes["pindex"]
|
7
|
-
l.error :object_with_components_and_pid if have_override && children.include?("components")
|
8
|
-
end
|
9
|
-
|
10
|
-
Log3mf.context "validating geometry" do |l|
|
11
|
-
list = EdgeList.new
|
12
|
-
|
13
|
-
# if a triangle has a pid, then the object needs a pid
|
14
|
-
has_triangle_pid = false
|
15
|
-
|
16
|
-
meshs = object.css('mesh')
|
17
|
-
meshs.each do |mesh|
|
18
|
-
|
19
|
-
num_vertices = mesh.css("vertex").count
|
20
|
-
triangles = mesh.css("triangle")
|
21
|
-
l.error :not_enough_triangles if triangles.count < 4
|
22
|
-
|
23
|
-
if triangles
|
24
|
-
triangles.each do |triangle|
|
25
|
-
|
26
|
-
v1 = triangle.attributes["v1"].to_s.to_i
|
27
|
-
v2 = triangle.attributes["v2"].to_s.to_i
|
28
|
-
v3 = triangle.attributes["v3"].to_s.to_i
|
29
|
-
|
30
|
-
l.error :invalid_vertex_index if [v1, v2, v3].select{|vertex| vertex >= num_vertices}.count > 0
|
31
|
-
|
32
|
-
unless includes_material
|
33
|
-
l.context "validating property overrides" do |l|
|
34
|
-
property_overrides = []
|
35
|
-
property_overrides << triangle.attributes['p1'].to_s.to_i if triangle.attributes['p1']
|
36
|
-
property_overrides << triangle.attributes['p2'].to_s.to_i if triangle.attributes['p2']
|
37
|
-
property_overrides << triangle.attributes['p3'].to_s.to_i if triangle.attributes['p3']
|
38
|
-
|
39
|
-
property_overrides.reject! { |prop| prop.nil? }
|
40
|
-
l.error :has_base_materials_gradient unless property_overrides.uniq.size <= 1
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
if v1 == v2 || v2 == v3 || v3 == v1
|
45
|
-
l.error :non_distinct_indices
|
46
|
-
end
|
47
|
-
|
48
|
-
list.add_edge(v1, v2)
|
49
|
-
list.add_edge(v2, v3)
|
50
|
-
list.add_edge(v3, v1)
|
51
|
-
unless has_triangle_pid
|
52
|
-
has_triangle_pid = triangle.attributes["pid"] != nil
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
if has_triangle_pid && !(object.attributes["pindex"] && object.attributes["pid"])
|
57
|
-
l.error :missing_object_pid
|
58
|
-
end
|
59
|
-
|
60
|
-
result = list.verify_edges
|
61
|
-
if result == :bad_orientation
|
62
|
-
l.error :resource_3dmodel_orientation
|
63
|
-
elsif result == :hole
|
64
|
-
l.error :resource_3dmodel_hole
|
65
|
-
elsif result == :nonmanifold
|
66
|
-
l.error :resource_3dmodel_nonmanifold
|
67
|
-
end
|
68
|
-
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def self.validate(model_doc, includes_material)
|
75
|
-
model_doc.css('model/resources/object').select { |object| ['model', 'solidsupport', ''].include?(object.attributes['type'].to_s) }.each do |object|
|
76
|
-
validate_object(object, includes_material)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
end
|
1
|
+
class MeshAnalyzer
|
2
|
+
|
3
|
+
def self.validate_object(object, includes_material)
|
4
|
+
Log3mf.context "verifying object" do |l|
|
5
|
+
children = object.children.map { |child| child.name }
|
6
|
+
have_override = object.attributes["pid"] or object.attributes["pindex"]
|
7
|
+
l.error :object_with_components_and_pid if have_override && children.include?("components")
|
8
|
+
end
|
9
|
+
|
10
|
+
Log3mf.context "validating geometry" do |l|
|
11
|
+
list = EdgeList.new
|
12
|
+
|
13
|
+
# if a triangle has a pid, then the object needs a pid
|
14
|
+
has_triangle_pid = false
|
15
|
+
|
16
|
+
meshs = object.css('mesh')
|
17
|
+
meshs.each do |mesh|
|
18
|
+
|
19
|
+
num_vertices = mesh.css("vertex").count
|
20
|
+
triangles = mesh.css("triangle")
|
21
|
+
l.error :not_enough_triangles if triangles.count < 4
|
22
|
+
|
23
|
+
if triangles
|
24
|
+
triangles.each do |triangle|
|
25
|
+
|
26
|
+
v1 = triangle.attributes["v1"].to_s.to_i
|
27
|
+
v2 = triangle.attributes["v2"].to_s.to_i
|
28
|
+
v3 = triangle.attributes["v3"].to_s.to_i
|
29
|
+
|
30
|
+
l.error :invalid_vertex_index if [v1, v2, v3].select{|vertex| vertex >= num_vertices}.count > 0
|
31
|
+
|
32
|
+
unless includes_material
|
33
|
+
l.context "validating property overrides" do |l|
|
34
|
+
property_overrides = []
|
35
|
+
property_overrides << triangle.attributes['p1'].to_s.to_i if triangle.attributes['p1']
|
36
|
+
property_overrides << triangle.attributes['p2'].to_s.to_i if triangle.attributes['p2']
|
37
|
+
property_overrides << triangle.attributes['p3'].to_s.to_i if triangle.attributes['p3']
|
38
|
+
|
39
|
+
property_overrides.reject! { |prop| prop.nil? }
|
40
|
+
l.error :has_base_materials_gradient unless property_overrides.uniq.size <= 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
if v1 == v2 || v2 == v3 || v3 == v1
|
45
|
+
l.error :non_distinct_indices
|
46
|
+
end
|
47
|
+
|
48
|
+
list.add_edge(v1, v2)
|
49
|
+
list.add_edge(v2, v3)
|
50
|
+
list.add_edge(v3, v1)
|
51
|
+
unless has_triangle_pid
|
52
|
+
has_triangle_pid = triangle.attributes["pid"] != nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
if has_triangle_pid && !(object.attributes["pindex"] && object.attributes["pid"])
|
57
|
+
l.error :missing_object_pid
|
58
|
+
end
|
59
|
+
|
60
|
+
result = list.verify_edges
|
61
|
+
if result == :bad_orientation
|
62
|
+
l.error :resource_3dmodel_orientation
|
63
|
+
elsif result == :hole
|
64
|
+
l.error :resource_3dmodel_hole
|
65
|
+
elsif result == :nonmanifold
|
66
|
+
l.error :resource_3dmodel_nonmanifold
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.validate(model_doc, includes_material)
|
75
|
+
model_doc.css('model/resources/object').select { |object| ['model', 'solidsupport', ''].include?(object.attributes['type'].to_s) }.each do |object|
|
76
|
+
validate_object(object, includes_material)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|