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/model3mf.rb
CHANGED
@@ -1,117 +1,120 @@
|
|
1
|
-
class Model3mf
|
2
|
-
|
3
|
-
MATERIAL_EXTENSION = 'http://schemas.microsoft.com/3dmanufacturing/material/2015/02'
|
4
|
-
SLICE_EXTENSION = 'http://schemas.microsoft.com/3dmanufacturing/slice/2015/07'
|
5
|
-
PRODUCTION_EXTENSION = 'http://schemas.microsoft.com/3dmanufacturing/production/2015/06'
|
6
|
-
|
7
|
-
|
8
|
-
MATERIAL_EXTENSION => {},
|
9
|
-
SLICE_EXTENSION => {},
|
10
|
-
PRODUCTION_EXTENSION => {}
|
11
|
-
}.freeze
|
12
|
-
|
13
|
-
VALID_CORE_METADATA_NAMES = ['Title', 'Designer', 'Description', 'Copyright', 'LicenseTerms', 'Rating', 'CreationDate', 'ModificationDate'].freeze
|
14
|
-
|
15
|
-
def self.parse(document, zip_entry)
|
16
|
-
model_doc = nil
|
17
|
-
|
18
|
-
Log3mf.context "parsing model" do |l|
|
19
|
-
begin
|
20
|
-
model_doc = XmlVal.validate_parse(zip_entry, SchemaFiles::SchemaTemplate)
|
21
|
-
rescue Nokogiri::XML::SyntaxError => e
|
22
|
-
l.fatal_error :model_invalid_xml, e: e
|
23
|
-
end
|
24
|
-
|
25
|
-
l.context "verifying
|
26
|
-
model_doc.css("//model").
|
27
|
-
|
28
|
-
|
29
|
-
if
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
end
|
1
|
+
class Model3mf
|
2
|
+
|
3
|
+
MATERIAL_EXTENSION = 'http://schemas.microsoft.com/3dmanufacturing/material/2015/02'
|
4
|
+
SLICE_EXTENSION = 'http://schemas.microsoft.com/3dmanufacturing/slice/2015/07'
|
5
|
+
PRODUCTION_EXTENSION = 'http://schemas.microsoft.com/3dmanufacturing/production/2015/06'
|
6
|
+
|
7
|
+
KNOWN_EXTENSIONS = {
|
8
|
+
MATERIAL_EXTENSION => {name: '3MF Materials and Properties Extension', supported: false},
|
9
|
+
SLICE_EXTENSION => {name: '3MF Slice Extension', supported: false},
|
10
|
+
PRODUCTION_EXTENSION => {name: '3MF Production Extension', supported: false}
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
VALID_CORE_METADATA_NAMES = ['Title', 'Designer', 'Description', 'Copyright', 'LicenseTerms', 'Rating', 'CreationDate', 'ModificationDate'].freeze
|
14
|
+
|
15
|
+
def self.parse(document, zip_entry)
|
16
|
+
model_doc = nil
|
17
|
+
|
18
|
+
Log3mf.context "parsing model" do |l|
|
19
|
+
begin
|
20
|
+
model_doc = XmlVal.validate_parse(zip_entry, SchemaFiles::SchemaTemplate)
|
21
|
+
rescue Nokogiri::XML::SyntaxError => e
|
22
|
+
l.fatal_error :model_invalid_xml, e: e
|
23
|
+
end
|
24
|
+
|
25
|
+
l.context "verifying supported extensions" do |l|
|
26
|
+
model_doc.css("//model").first.namespaces.each do |prefix, uri|
|
27
|
+
unless prefix == "xmlns"
|
28
|
+
ext = KNOWN_EXTENSIONS[uri]
|
29
|
+
if ext.nil? || !ext[:supported]
|
30
|
+
l.warning :unsupported_extension, ext: (ext.nil? ? uri : ext[:name])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
l.context "verifying requiredextensions" do |l|
|
37
|
+
model_doc.css("//model").map{|node| node.attributes["requiredextensions"]}.compact.each do |required_extension|
|
38
|
+
required_extension.value.split(" ").each do |ns|
|
39
|
+
namespace_uri = model_doc.namespaces["xmlns:#{ns}"]
|
40
|
+
l.error :missing_extension_namespace_uri, ns: ns unless namespace_uri
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
l.context "verifying 3D payload required resources" do |l|
|
46
|
+
# results = model_doc.css("model resources m:texture2d")
|
47
|
+
required_resources = model_doc.css("//model//resources//*[path]").collect { |n| n["path"] }
|
48
|
+
required_resources += model_doc.css("//model//resources//object[thumbnail]").collect { |n| n["thumbnail"] }
|
49
|
+
|
50
|
+
# for each, ensure that they exist in m.relationships
|
51
|
+
relationship_resources = []
|
52
|
+
rel_file = "#{Pathname(zip_entry.name).dirname.to_s}/_rels/#{File.basename(zip_entry.name)}.rels"
|
53
|
+
relationships = document.relationships[rel_file]
|
54
|
+
unless (relationships.nil?)
|
55
|
+
relationship_resources = relationships.map { |relationship| relationship[:target] }
|
56
|
+
end
|
57
|
+
|
58
|
+
missing_resources = (required_resources - relationship_resources)
|
59
|
+
if missing_resources.empty?
|
60
|
+
l.info "All model required resources are defined in .rels relationship files."
|
61
|
+
else
|
62
|
+
missing_resources.each { |mr|
|
63
|
+
l.error :model_resource_not_in_rels, mr: mr
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
l.context 'verifying resources' do |l|
|
70
|
+
resources = model_doc.root.css("resources")
|
71
|
+
if resources
|
72
|
+
ids = resources.children.map { |child| child.attributes['id'].to_s if child.attributes['id'] }
|
73
|
+
l.error :resource_id_collision if ids.uniq.size != ids.size
|
74
|
+
pids = resources.children.map { |child| child.attributes['pid'].to_s }
|
75
|
+
missing_pids = pids.select { |pid| !pid.empty? and !ids.include? pid }
|
76
|
+
missing_pids.each do |p|
|
77
|
+
l.error :resource_pid_missing, pid: p
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
l.context "verifying build items" do |l|
|
83
|
+
|
84
|
+
l.error :build_with_other_item if model_doc.css('build/item').map { |x| x.attributes["objectid"].value }.map{ |id| model_doc.search(".//xmlns:object[@id=$id][@type=$type]", nil, { :id => id, :type => 'other' } ) }.flatten.any?
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
l.context "checking metadata" do |l|
|
89
|
+
metadata_names = model_doc.root.css("metadata").map { |met| met['name'] }
|
90
|
+
l.error :metadata_elements_with_same_name unless metadata_names.uniq!.nil?
|
91
|
+
|
92
|
+
unless (metadata_names - VALID_CORE_METADATA_NAMES).empty?
|
93
|
+
extra_names = metadata_names - VALID_CORE_METADATA_NAMES
|
94
|
+
ns_names = extra_names.select { |n| n.include? ':' }
|
95
|
+
|
96
|
+
l.error :invalid_metadata_under_defaultns unless (extra_names - ns_names).empty?
|
97
|
+
|
98
|
+
unless ns_names.empty?
|
99
|
+
prefixes = model_doc.root.namespace_definitions.map { |defs| defs.prefix }.reject { |pre| pre.nil? }
|
100
|
+
l.error :invalid_metadata_name unless (ns_names.collect { |i| i.split(':').first } - prefixes).empty?
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
includes_material = model_doc.namespaces.values.include?(MATERIAL_EXTENSION)
|
106
|
+
MeshAnalyzer.validate(model_doc, includes_material)
|
107
|
+
|
108
|
+
l.context "verifying triangle normal" do |l|
|
109
|
+
model_doc.css('model/resources/object').select { |object| ['model', 'solidsupport', ''].include?(object.attributes['type'].to_s) }.each do |object|
|
110
|
+
meshes = object.css('mesh')
|
111
|
+
meshes.each do |mesh|
|
112
|
+
processor = MeshNormalAnalyzer.new(mesh)
|
113
|
+
l.error :inward_facing_normal if processor.found_inward_triangle
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
model_doc
|
119
|
+
end
|
120
|
+
end
|
@@ -1,50 +1,50 @@
|
|
1
|
-
class Relationships
|
2
|
-
|
3
|
-
def self.parse(zip_entry)
|
4
|
-
relationships = []
|
5
|
-
Log3mf.context "parsing relationships" do |l|
|
6
|
-
begin
|
7
|
-
# Parse Relationships XML
|
8
|
-
doc = XmlVal.validate_parse(zip_entry)
|
9
|
-
|
10
|
-
# Verify <Relationships><Relationship/></Relationships>
|
11
|
-
root_element = doc.children[0]
|
12
|
-
if root_element.name == "Relationships"
|
13
|
-
relationship_elements = root_element.children
|
14
|
-
if relationship_elements.size > 0
|
15
|
-
relationship_elements.each do |node|
|
16
|
-
if node.is_a?(Nokogiri::XML::Element) && node.name == "Relationship"
|
17
|
-
l.error :multiple_relationships if (relationships.select { |r| r[:target] == node['Target'] && r[:type] == node['Type'] }.size > 0)
|
18
|
-
l.error :non_unique_rel_id, :file => zip_entry.name, :id => node['Id'] if (relationships.select { |r| r[:id] == node['Id'] }.size > 0)
|
19
|
-
relationships << {target: node['Target'], type: node['Type'], id: node['Id']}
|
20
|
-
l.info "adding relationship: #{relationships.last.inspect}"
|
21
|
-
else
|
22
|
-
unless node.is_a? Nokogiri::XML::Text
|
23
|
-
l.info "found non-Relationship node: #{node.name}"
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
if zip_entry.name=="_rels/.rels"
|
29
|
-
l.context "Verifying StartPart" do |l|
|
30
|
-
start_part_type = Document::MODEL_TYPE
|
31
|
-
start_part_count = relationships.select { |r| r[:type] == start_part_type }.size
|
32
|
-
if start_part_count != 1
|
33
|
-
l.error :invalid_startpart_type
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
else
|
38
|
-
l.error :dot_rels_file_no_relationship_element
|
39
|
-
end
|
40
|
-
else
|
41
|
-
l.error :dot_rels_file_missing_relationships_element
|
42
|
-
end
|
43
|
-
|
44
|
-
rescue Nokogiri::XML::SyntaxError => e
|
45
|
-
l.error :dot_rels_file_has_invalid_xml, e: "#{e.message}"
|
46
|
-
end
|
47
|
-
end
|
48
|
-
relationships
|
49
|
-
end
|
50
|
-
end
|
1
|
+
class Relationships
|
2
|
+
|
3
|
+
def self.parse(zip_entry)
|
4
|
+
relationships = []
|
5
|
+
Log3mf.context "parsing relationships" do |l|
|
6
|
+
begin
|
7
|
+
# Parse Relationships XML
|
8
|
+
doc = XmlVal.validate_parse(zip_entry)
|
9
|
+
|
10
|
+
# Verify <Relationships><Relationship/></Relationships>
|
11
|
+
root_element = doc.children[0]
|
12
|
+
if root_element.name == "Relationships"
|
13
|
+
relationship_elements = root_element.children
|
14
|
+
if relationship_elements.size > 0
|
15
|
+
relationship_elements.each do |node|
|
16
|
+
if node.is_a?(Nokogiri::XML::Element) && node.name == "Relationship"
|
17
|
+
l.error :multiple_relationships if (relationships.select { |r| r[:target] == node['Target'] && r[:type] == node['Type'] }.size > 0)
|
18
|
+
l.error :non_unique_rel_id, :file => zip_entry.name, :id => node['Id'] if (relationships.select { |r| r[:id] == node['Id'] }.size > 0)
|
19
|
+
relationships << {target: node['Target'], type: node['Type'], id: node['Id']}
|
20
|
+
l.info "adding relationship: #{relationships.last.inspect}"
|
21
|
+
else
|
22
|
+
unless node.is_a? Nokogiri::XML::Text
|
23
|
+
l.info "found non-Relationship node: #{node.name}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
if zip_entry.name=="_rels/.rels"
|
29
|
+
l.context "Verifying StartPart" do |l|
|
30
|
+
start_part_type = Document::MODEL_TYPE
|
31
|
+
start_part_count = relationships.select { |r| r[:type] == start_part_type }.size
|
32
|
+
if start_part_count != 1
|
33
|
+
l.error :invalid_startpart_type
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
else
|
38
|
+
l.error :dot_rels_file_no_relationship_element
|
39
|
+
end
|
40
|
+
else
|
41
|
+
l.error :dot_rels_file_missing_relationships_element
|
42
|
+
end
|
43
|
+
|
44
|
+
rescue Nokogiri::XML::SyntaxError => e
|
45
|
+
l.error :dot_rels_file_has_invalid_xml, e: "#{e.message}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
relationships
|
49
|
+
end
|
50
|
+
end
|
data/lib/ruby3mf/schema_files.rb
CHANGED
@@ -1,26 +1,26 @@
|
|
1
|
-
require 'erb'
|
2
|
-
|
3
|
-
class SchemaFiles
|
4
|
-
|
5
|
-
SchemaTemplate = File.join(File.dirname(__FILE__), "3MFcoreSpec_1.1.xsd.template")
|
6
|
-
SchemaLocation = File.join(File.dirname(__FILE__), "xml.xsd")
|
7
|
-
|
8
|
-
class << self
|
9
|
-
|
10
|
-
def open(file)
|
11
|
-
@@template ||= File.open(file, "r") do |file|
|
12
|
-
file.read
|
13
|
-
end
|
14
|
-
|
15
|
-
yield(SchemaFiles.render)
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
def render
|
20
|
-
@@xsd_content ||= ERB.new(@@template).result( binding )
|
21
|
-
end
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
|
26
|
-
end
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
class SchemaFiles
|
4
|
+
|
5
|
+
SchemaTemplate = File.join(File.dirname(__FILE__), "3MFcoreSpec_1.1.xsd.template")
|
6
|
+
SchemaLocation = File.join(File.dirname(__FILE__), "xml.xsd")
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def open(file)
|
11
|
+
@@template ||= File.open(file, "r") do |file|
|
12
|
+
file.read
|
13
|
+
end
|
14
|
+
|
15
|
+
yield(SchemaFiles.render)
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
def render
|
20
|
+
@@xsd_content ||= ERB.new(@@template).result( binding )
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
end
|
data/lib/ruby3mf/texture3mf.rb
CHANGED
@@ -1,29 +1,29 @@
|
|
1
|
-
class Texture3mf
|
2
|
-
attr_accessor :name
|
3
|
-
|
4
|
-
def initialize(document)
|
5
|
-
@doc = document
|
6
|
-
end
|
7
|
-
|
8
|
-
def self.parse(document, relationship_file)
|
9
|
-
t = new(document)
|
10
|
-
t.name = relationship_file.name
|
11
|
-
stream = relationship_file.get_input_stream
|
12
|
-
img_type = MimeMagic.by_magic(stream)
|
13
|
-
Log3mf.context "Texture3mf" do |l|
|
14
|
-
l.fatal_error :zero_size_texture unless img_type
|
15
|
-
l.debug "texture is of type: #{img_type}"
|
16
|
-
l.error(:invalid_texture_file_type, type: img_type) unless ['image/png', 'image/jpeg'].include? img_type.type
|
17
|
-
end
|
18
|
-
t
|
19
|
-
end
|
20
|
-
|
21
|
-
def update(bytes)
|
22
|
-
@doc.objects[name]=bytes
|
23
|
-
end
|
24
|
-
|
25
|
-
def contents
|
26
|
-
@doc.objects[name] || @doc.contents_for(name)
|
27
|
-
end
|
28
|
-
|
29
|
-
end
|
1
|
+
class Texture3mf
|
2
|
+
attr_accessor :name
|
3
|
+
|
4
|
+
def initialize(document)
|
5
|
+
@doc = document
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.parse(document, relationship_file)
|
9
|
+
t = new(document)
|
10
|
+
t.name = relationship_file.name
|
11
|
+
stream = relationship_file.get_input_stream
|
12
|
+
img_type = MimeMagic.by_magic(stream)
|
13
|
+
Log3mf.context "Texture3mf" do |l|
|
14
|
+
l.fatal_error :zero_size_texture unless img_type
|
15
|
+
l.debug "texture is of type: #{img_type}"
|
16
|
+
l.error(:invalid_texture_file_type, type: img_type) unless ['image/png', 'image/jpeg'].include? img_type.type
|
17
|
+
end
|
18
|
+
t
|
19
|
+
end
|
20
|
+
|
21
|
+
def update(bytes)
|
22
|
+
@doc.objects[name]=bytes
|
23
|
+
end
|
24
|
+
|
25
|
+
def contents
|
26
|
+
@doc.objects[name] || @doc.contents_for(name)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/lib/ruby3mf/thumbnail3mf.rb
CHANGED
@@ -1,21 +1,21 @@
|
|
1
|
-
require 'mini_magick'
|
2
|
-
|
3
|
-
class Thumbnail3mf
|
4
|
-
|
5
|
-
def self.parse(doc, relationship_file)
|
6
|
-
Log3mf.context "Thumbnail3mf" do |l|
|
7
|
-
|
8
|
-
img_type = MimeMagic.by_magic(relationship_file.get_input_stream)
|
9
|
-
l.fatal_error :invalid_thumbnail_file unless img_type
|
10
|
-
l.error(:invalid_thumbnail_file_type, type: img_type) unless ['image/png', 'image/jpeg'].include? img_type.type
|
11
|
-
|
12
|
-
img_colorspace = MiniMagick::Image.read(relationship_file.get_input_stream).colorspace
|
13
|
-
l.fatal_error :invalid_thumbnail_colorspace if img_colorspace.include? "CMYK"
|
14
|
-
|
15
|
-
if relationship_file.respond_to?(:name)
|
16
|
-
decl_type = doc.types.get_type(relationship_file.name)
|
17
|
-
l.error :thumbnail_image_type_mismatch if decl_type != img_type.to_s
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
1
|
+
require 'mini_magick'
|
2
|
+
|
3
|
+
class Thumbnail3mf
|
4
|
+
|
5
|
+
def self.parse(doc, relationship_file)
|
6
|
+
Log3mf.context "Thumbnail3mf" do |l|
|
7
|
+
|
8
|
+
img_type = MimeMagic.by_magic(relationship_file.get_input_stream)
|
9
|
+
l.fatal_error :invalid_thumbnail_file unless img_type
|
10
|
+
l.error(:invalid_thumbnail_file_type, type: img_type) unless ['image/png', 'image/jpeg'].include? img_type.type
|
11
|
+
|
12
|
+
img_colorspace = MiniMagick::Image.read(relationship_file.get_input_stream).colorspace
|
13
|
+
l.fatal_error :invalid_thumbnail_colorspace if img_colorspace.include? "CMYK"
|
14
|
+
|
15
|
+
if relationship_file.respond_to?(:name)
|
16
|
+
decl_type = doc.types.get_type(relationship_file.name)
|
17
|
+
l.error :thumbnail_image_type_mismatch if decl_type != img_type.to_s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/ruby3mf/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
module Ruby3mf
|
2
|
-
VERSION = "0.2.
|
3
|
-
end
|
1
|
+
module Ruby3mf
|
2
|
+
VERSION = "0.2.7"
|
3
|
+
end
|