ruby3mf 0.2.6 → 0.2.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|