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.
@@ -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
- VALID_EXTENSIONS = {
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 requiredextensions" do |l|
26
- model_doc.css("//model").map{|node| node.attributes["requiredextensions"]}.compact.each do |required_extension|
27
- required_extension.value.split(" ").each do |ns|
28
- namespace_uri = model_doc.namespaces["xmlns:#{ns}"]
29
- if namespace_uri
30
- if VALID_EXTENSIONS.has_key? namespace_uri
31
- l.info "Found a valid required extension: #{namespace_uri}"
32
- else
33
- l.error :unknown_required_extension, ext: namespace_uri
34
- end
35
- else
36
- l.error :missing_extension_namespace_uri, ns: ns
37
- end
38
- end
39
- end
40
- end
41
-
42
- l.context "verifying 3D payload required resources" do |l|
43
- # results = model_doc.css("model resources m:texture2d")
44
- required_resources = model_doc.css("//model//resources//*[path]").collect { |n| n["path"] }
45
- required_resources += model_doc.css("//model//resources//object[thumbnail]").collect { |n| n["thumbnail"] }
46
-
47
- # for each, ensure that they exist in m.relationships
48
- relationship_resources = []
49
- rel_file = "#{Pathname(zip_entry.name).dirname.to_s}/_rels/#{File.basename(zip_entry.name)}.rels"
50
- relationships = document.relationships[rel_file]
51
- unless (relationships.nil?)
52
- relationship_resources = relationships.map { |relationship| relationship[:target] }
53
- end
54
-
55
- missing_resources = (required_resources - relationship_resources)
56
- if missing_resources.empty?
57
- l.info "All model required resources are defined in .rels relationship files."
58
- else
59
- missing_resources.each { |mr|
60
- l.error :model_resource_not_in_rels, mr: mr
61
- }
62
- end
63
-
64
- end
65
-
66
- l.context 'verifying resources' do |l|
67
- resources = model_doc.root.css("resources")
68
- if resources
69
- ids = resources.children.map { |child| child.attributes['id'].to_s if child.attributes['id'] }
70
- l.error :resource_id_collision if ids.uniq.size != ids.size
71
- pids = resources.children.map { |child| child.attributes['pid'].to_s }
72
- missing_pids = pids.select { |pid| !pid.empty? and !ids.include? pid }
73
- missing_pids.each do |p|
74
- l.error :resource_pid_missing, pid: p
75
- end
76
- end
77
- end
78
-
79
- l.context "verifying build items" do |l|
80
-
81
- 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?
82
-
83
- end
84
-
85
- l.context "checking metadata" do |l|
86
- metadata_names = model_doc.root.css("metadata").map { |met| met['name'] }
87
- l.error :metadata_elements_with_same_name unless metadata_names.uniq!.nil?
88
-
89
- unless (metadata_names - VALID_CORE_METADATA_NAMES).empty?
90
- extra_names = metadata_names - VALID_CORE_METADATA_NAMES
91
- ns_names = extra_names.select { |n| n.include? ':' }
92
-
93
- l.error :invalid_metadata_under_defaultns unless (extra_names - ns_names).empty?
94
-
95
- unless ns_names.empty?
96
- prefixes = model_doc.root.namespace_definitions.map { |defs| defs.prefix }.reject { |pre| pre.nil? }
97
- l.error :invalid_metadata_name unless (ns_names.collect { |i| i.split(':').first } - prefixes).empty?
98
- end
99
- end
100
- end
101
-
102
- includes_material = model_doc.namespaces.values.include?(MATERIAL_EXTENSION)
103
- MeshAnalyzer.validate(model_doc, includes_material)
104
-
105
- l.context "verifying triangle normal" do |l|
106
- model_doc.css('model/resources/object').select { |object| ['model', 'solidsupport', ''].include?(object.attributes['type'].to_s) }.each do |object|
107
- meshes = object.css('mesh')
108
- meshes.each do |mesh|
109
- processor = MeshNormalAnalyzer.new(mesh)
110
- l.error :inward_facing_normal if processor.found_inward_triangle
111
- end
112
- end
113
- end
114
- end
115
- model_doc
116
- end
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
@@ -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
@@ -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
@@ -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
@@ -1,3 +1,3 @@
1
- module Ruby3mf
2
- VERSION = "0.2.6"
3
- end
1
+ module Ruby3mf
2
+ VERSION = "0.2.7"
3
+ end