ruby3mf 0.1.11 → 0.1.13
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/cli.rb +7 -0
- data/lib/ruby3mf.rb +3 -2
- data/lib/ruby3mf/content_types.rb +1 -3
- data/lib/ruby3mf/document.rb +33 -2
- data/lib/ruby3mf/edge_list.rb +61 -0
- data/lib/ruby3mf/errors.yml +96 -0
- data/lib/ruby3mf/global_xml_validations.rb +36 -0
- data/lib/ruby3mf/log3mf.rb +14 -3
- data/lib/ruby3mf/mesh_analyzer.rb +78 -0
- data/lib/ruby3mf/model3mf.rb +15 -7
- data/lib/ruby3mf/relationships.rb +7 -5
- data/lib/ruby3mf/texture3mf.rb +0 -1
- data/lib/ruby3mf/thumbnail3mf.rb +6 -0
- data/lib/ruby3mf/version.rb +1 -1
- data/ruby3mf.gemspec +1 -8
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53cf0043b4bcebef905de98f2137ad6db1b36fc4
|
4
|
+
data.tar.gz: 3a970b15e198109bf6638f7206569e0b90bc51fc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 91a20f9e6da5bf9f89b582509764e0ef3cf0a320c798c0020fd5fc4fd6e9e1a931193005549c0777e30b19292fcb33b7b044fee6ff70ba6ec8b36fcd0411e7e7
|
7
|
+
data.tar.gz: a2203b11cb4f4ecdd9ec342ae73ae5fa2b59e46d58474671ff2123e7dd88c3438c85d7cfe54bfb3c59ac024f501dfd8ed6a818a43647dc02d9bb3866ca137095
|
data/bin/cli.rb
CHANGED
@@ -3,3 +3,10 @@
|
|
3
3
|
require_relative '../lib/ruby3mf'
|
4
4
|
|
5
5
|
doc = Document.read(ARGV.first)
|
6
|
+
|
7
|
+
errors = Log3mf.entries(:error, :fatal_error)
|
8
|
+
puts "Validating file: #{ARGV.first}"
|
9
|
+
errors.each do |ent|
|
10
|
+
h = { context: ent[0], severity: ent[1], message: ent[2] }
|
11
|
+
puts h
|
12
|
+
end
|
data/lib/ruby3mf.rb
CHANGED
@@ -6,14 +6,15 @@ require_relative "ruby3mf/model3mf"
|
|
6
6
|
require_relative "ruby3mf/relationships"
|
7
7
|
require_relative "ruby3mf/thumbnail3mf"
|
8
8
|
require_relative "ruby3mf/texture3mf"
|
9
|
+
require_relative "ruby3mf/global_xml_validations"
|
9
10
|
|
10
11
|
require 'zip'
|
11
12
|
require 'nokogiri'
|
12
13
|
require 'json'
|
13
14
|
require 'mimemagic'
|
14
|
-
|
15
|
+
require 'uri'
|
16
|
+
require 'yaml'
|
15
17
|
|
16
18
|
module Ruby3mf
|
17
|
-
|
18
19
|
# Your code goes here...
|
19
20
|
end
|
@@ -6,9 +6,7 @@ class ContentTypes
|
|
6
6
|
Log3mf.context "parse" do |l|
|
7
7
|
begin
|
8
8
|
|
9
|
-
doc =
|
10
|
-
config.strict.nonet.noblanks
|
11
|
-
end
|
9
|
+
doc = GlobalXMLValidations.validate_parse(zip_entry)
|
12
10
|
|
13
11
|
l.warning '[Content_Types].xml must contain exactly one root node' unless doc.children.size == 1
|
14
12
|
l.warning '[Content_Types].xml must contain root name Types' unless doc.children.first.name == "Types"
|
data/lib/ruby3mf/document.rb
CHANGED
@@ -28,7 +28,6 @@ class Document
|
|
28
28
|
def self.read(input_file)
|
29
29
|
|
30
30
|
m = new(input_file)
|
31
|
-
|
32
31
|
begin
|
33
32
|
Log3mf.context "zip" do |l|
|
34
33
|
begin
|
@@ -43,6 +42,25 @@ class Document
|
|
43
42
|
|
44
43
|
l.info "Zip file is valid"
|
45
44
|
|
45
|
+
# check for valid, absolute URI's for each path name
|
46
|
+
|
47
|
+
zip_file.each do |part|
|
48
|
+
l.context "part names /#{part.name}" do |l|
|
49
|
+
unless part.name.end_with? '[Content_Types].xml'
|
50
|
+
begin
|
51
|
+
u = URI part.name
|
52
|
+
rescue ArgumentError
|
53
|
+
l.error :err_uri_bad
|
54
|
+
next
|
55
|
+
end
|
56
|
+
|
57
|
+
u.path.split('/').each do |segment|
|
58
|
+
l.error :err_uri_hidden_file if (segment.start_with? '.') && !(segment.end_with? '.rels')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
46
64
|
l.context "content types" do |l|
|
47
65
|
# 1. Get Content Types
|
48
66
|
content_type_match = zip_file.glob('\[Content_Types\].xml').first
|
@@ -71,7 +89,19 @@ class Document
|
|
71
89
|
# 3. Validate all relationships
|
72
90
|
m.relationships.each do |rel|
|
73
91
|
l.context rel[:target] do |l|
|
92
|
+
|
93
|
+
begin
|
94
|
+
u = URI rel[:target]
|
95
|
+
rescue URI::InvalidURIError
|
96
|
+
l.error :err_uri_bad
|
97
|
+
next
|
98
|
+
end
|
99
|
+
|
100
|
+
l.error :err_uri_relative_path unless u.to_s.start_with? '/'
|
101
|
+
|
74
102
|
target = rel[:target].gsub(/^\//, "")
|
103
|
+
l.error :err_uri_empty_segment if target.end_with? '/' or target.include? '//'
|
104
|
+
l.error :err_uri_relative_path if target.include? '/../'
|
75
105
|
relationship_file = zip_file.glob(target).first
|
76
106
|
|
77
107
|
if relationship_file
|
@@ -92,6 +122,7 @@ class Document
|
|
92
122
|
end
|
93
123
|
end
|
94
124
|
end
|
125
|
+
|
95
126
|
return m
|
96
127
|
rescue Zip::Error
|
97
128
|
l.fatal_error 'File provided is not a valid ZIP archive', page: 9
|
@@ -123,7 +154,7 @@ class Document
|
|
123
154
|
end
|
124
155
|
end
|
125
156
|
|
126
|
-
File.open(output_file, "wb") {|f| f.write(buffer.string) }
|
157
|
+
File.open(output_file, "wb") { |f| f.write(buffer.string) }
|
127
158
|
|
128
159
|
end
|
129
160
|
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class EdgeList
|
2
|
+
|
3
|
+
def initialize()
|
4
|
+
@edges = { }
|
5
|
+
end
|
6
|
+
|
7
|
+
def add_edge(first_vertex, second_vertex)
|
8
|
+
if first_vertex < second_vertex
|
9
|
+
edge = "#{first_vertex}:#{second_vertex}"
|
10
|
+
else
|
11
|
+
edge = "#{second_vertex}:#{first_vertex}"
|
12
|
+
end
|
13
|
+
|
14
|
+
(pos_count, neg_count) = @edges[edge]
|
15
|
+
|
16
|
+
if pos_count == nil or neg_count == nil
|
17
|
+
pos_count = 0
|
18
|
+
neg_count = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
pos_count += 1 if first_vertex < second_vertex
|
22
|
+
neg_count += 1 if second_vertex < first_vertex
|
23
|
+
|
24
|
+
@edges[edge] = [pos_count, neg_count]
|
25
|
+
end
|
26
|
+
|
27
|
+
def edge_count(first_vertex, second_vertex)
|
28
|
+
if first_vertex < second_vertex
|
29
|
+
edge = "#{first_vertex}:#{second_vertex}"
|
30
|
+
else
|
31
|
+
edge = "#{second_vertex}:#{first_vertex}"
|
32
|
+
end
|
33
|
+
|
34
|
+
@edges[edge]
|
35
|
+
end
|
36
|
+
|
37
|
+
def print_list()
|
38
|
+
@edges.each do |key, value|
|
39
|
+
(pos, neg) = value
|
40
|
+
puts "#{pos} : #{neg}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def verify_edges()
|
45
|
+
@edges.each do |key, value|
|
46
|
+
(pos, neg) = value
|
47
|
+
|
48
|
+
if (pos > 1 and neg == 0) or (pos == 0 and neg > 1)
|
49
|
+
return :bad_orientation
|
50
|
+
elsif pos + neg == 1
|
51
|
+
return :hole
|
52
|
+
elsif pos != 1 or neg != 1
|
53
|
+
return :nonmanifold
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
:ok
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
@@ -0,0 +1,96 @@
|
|
1
|
+
resource_contentype_invalid:
|
2
|
+
msg: "resource in model has invalid contenttype %{bt}"
|
3
|
+
page: 10
|
4
|
+
err_uri_empty_segment:
|
5
|
+
msg: 'No segment of a 3MF part name path may be empty'
|
6
|
+
page: 13
|
7
|
+
err_uri_hidden_file:
|
8
|
+
msg: "Other than /_rels/.rels, no segment of a 3MF part name may start with the '.' character"
|
9
|
+
page: 13
|
10
|
+
err_uri_bad:
|
11
|
+
msg: 'Path names must be valid Open Package Convention URIs or IRIs'
|
12
|
+
page: 13
|
13
|
+
err_uri_relative_path:
|
14
|
+
msg: 'Part names must not include relative paths'
|
15
|
+
page: 13
|
16
|
+
model_resource_not_in_rels:
|
17
|
+
msg: "Missing required resource: %{mr} Resource referenced in model, but not in .rels relationship file"
|
18
|
+
page: 10
|
19
|
+
resource_3dmodel_orientation:
|
20
|
+
msg: "Bad triangle orientation"
|
21
|
+
page: 27
|
22
|
+
resource_3dmodel_hole:
|
23
|
+
msg: "Hole in model"
|
24
|
+
page: 27
|
25
|
+
resource_3dmodel_nonmanifold:
|
26
|
+
msg: "Non-manifold edge in 3dmodel"
|
27
|
+
page: 27
|
28
|
+
3d_model_invalid_xml:
|
29
|
+
msg: "Model file invalid XML. Exception Start tag expected, '<' not found"
|
30
|
+
page:
|
31
|
+
3d_payload_files:
|
32
|
+
msg: "Relationship Target file /3D/Texture/texture2.texture not found"
|
33
|
+
page: 11
|
34
|
+
content_types_invalid_xml:
|
35
|
+
msg: "[Content_Types].xml file is not valid XML. Start tag expected, '<' not found"
|
36
|
+
page: 15
|
37
|
+
dot_rels_file_has_invalid_xml:
|
38
|
+
msg: "Relationships (.rel) file is not a valid XML file: Start tag expected, '<' not found"
|
39
|
+
page: 4
|
40
|
+
dot_rels_file_missing_relationships_element:
|
41
|
+
msg: ".rels XML must have <Relationships> root element"
|
42
|
+
page: 4
|
43
|
+
dot_rels_file_no_relationship_element:
|
44
|
+
msg: "No relationship elements found"
|
45
|
+
page: 4
|
46
|
+
invalid_content_type:
|
47
|
+
msg: "[Content_Types].xml is missing required ContentType \"application/vnd.openxmlformats-package.relationships+xml\""
|
48
|
+
page: 10
|
49
|
+
invalid_startpart_type:
|
50
|
+
msg: "rels/.rels Relationship file has an invalid attribute type for the root 3D Model (StartPart). The required type is \"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel\""
|
51
|
+
page: 10
|
52
|
+
invalid_thumbnail_colorspace:
|
53
|
+
msg: "CMYK JPEG images must not be used for the thumbnail"
|
54
|
+
page: 36
|
55
|
+
invalid_thumbnail_file_type:
|
56
|
+
msg: "Expected a png or jpeg thumbnail but the thumbnail was of type image/gif"
|
57
|
+
page: 36
|
58
|
+
invalid_texture_file_type:
|
59
|
+
msg: "Expected a png or jpeg texture but the texture was of type image/gif"
|
60
|
+
page: 16
|
61
|
+
missing_content_types:
|
62
|
+
msg: "Missing required file: [Content_Types].xml"
|
63
|
+
page: 4
|
64
|
+
missing_dot_rels_file:
|
65
|
+
msg: "Missing required file _rels/.rels"
|
66
|
+
page: 4
|
67
|
+
missing_rels_folder:
|
68
|
+
msg: "Missing required file _rels/.rels"
|
69
|
+
page: 4
|
70
|
+
multiple_relationships:
|
71
|
+
msg: "There MUST NOT be more than one relationship of a given relationship type from one part to a second part"
|
72
|
+
page: 10
|
73
|
+
no_3d_model:
|
74
|
+
msg: "Relationship Target file /3D/3dmodel.model not found"
|
75
|
+
page: 11
|
76
|
+
not_a_zip:
|
77
|
+
msg: "File provided is not a valid ZIP archive"
|
78
|
+
page: 9
|
79
|
+
zero_size_texture:
|
80
|
+
msg: "Texture file must be valid image file"
|
81
|
+
page: 16
|
82
|
+
resource_contentype_invalid:
|
83
|
+
msg: "resource in model has invalid contenttype image/ping"
|
84
|
+
page: 10
|
85
|
+
has_xml_space_attribute:
|
86
|
+
msg: "found an xml:space attribute when it is not allowed"
|
87
|
+
page: 16
|
88
|
+
wrong_encoding:
|
89
|
+
msg: "found XML content that was not UTF8 encoded"
|
90
|
+
page: 15
|
91
|
+
missing_object_pid:
|
92
|
+
msg: "object with triangle color override missing object level pid"
|
93
|
+
page: 26
|
94
|
+
missing_model_children:
|
95
|
+
msg: "model element must include resources and build as child elements"
|
96
|
+
page: 20
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class GlobalXMLValidations
|
2
|
+
|
3
|
+
def self.validate_parse(file)
|
4
|
+
doc = Nokogiri::XML(file.get_input_stream) do |config|
|
5
|
+
config.strict.nonet.noblanks
|
6
|
+
end
|
7
|
+
validate(file, doc)
|
8
|
+
doc
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.validate(file, document)
|
12
|
+
Log3mf.context "global xml validations" do |l|
|
13
|
+
l.error :has_xml_space_attribute if space_attribute_exists?(document)
|
14
|
+
l.error :wrong_encoding if xml_not_utf8_encoded?(document)
|
15
|
+
l.error :dtd_not_allowed if dtd_exists?(file)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.space_attribute_exists?(document)
|
20
|
+
!(document.xpath('//*[@xml:space]').empty?)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.xml_not_utf8_encoded?(document)
|
24
|
+
!(document.encoding == 'UTF-8' || document.encoding == 'utf-8')
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.dtd_exists?(file)
|
28
|
+
found = file.get_input_stream.find { |line| line =~ /(!DOCTYPE\b)|(!ELEMENT\b)|(!ENTITY\b)|(!NOTATION\b)|(!ATTLIST\b)/ }
|
29
|
+
!found.nil?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
|
data/lib/ruby3mf/log3mf.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'singleton'
|
2
|
+
require 'yaml'
|
2
3
|
|
3
4
|
# Example usage:
|
4
5
|
|
@@ -33,6 +34,8 @@ class Log3mf
|
|
33
34
|
@log_list = []
|
34
35
|
@context_stack = []
|
35
36
|
@ledger = []
|
37
|
+
errormap_path = File.join(File.dirname(__FILE__),"errors.yml")
|
38
|
+
@errormap = YAML.load_file(errormap_path)
|
36
39
|
end
|
37
40
|
|
38
41
|
def reset_log
|
@@ -68,11 +71,20 @@ class Log3mf
|
|
68
71
|
end
|
69
72
|
|
70
73
|
def log(severity, message, options={})
|
71
|
-
|
72
|
-
|
74
|
+
if message.is_a?(Symbol)
|
75
|
+
new_log(severity, message, options)
|
76
|
+
else
|
77
|
+
@log_list << ["#{@context_stack.join("/")}", severity, message, options] unless severity==:debug && ENV['LOGDEBUG'].nil?
|
78
|
+
# puts "[#{@context_stack.join("/")}] #{severity.to_s.upcase} #{message}"
|
79
|
+
end
|
73
80
|
raise FatalError if severity == :fatal_error
|
74
81
|
end
|
75
82
|
|
83
|
+
def new_log(severity, message, options={})
|
84
|
+
error_info = @errormap.fetch(message.to_s)
|
85
|
+
@log_list << ["#{@context_stack.join("/")}", severity, error_info["msg"], page: error_info["page"]] unless severity==:debug && ENV['LOGDEBUG'].nil?
|
86
|
+
end
|
87
|
+
|
76
88
|
def count_entries(*levels)
|
77
89
|
entries(*levels).count
|
78
90
|
end
|
@@ -120,4 +132,3 @@ class Log3mf
|
|
120
132
|
Log3mf.instance.to_json
|
121
133
|
end
|
122
134
|
end
|
123
|
-
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require_relative 'edge_list'
|
2
|
+
|
3
|
+
|
4
|
+
def find_child(node, child_name)
|
5
|
+
node.children.each do |child|
|
6
|
+
if child.name == child_name
|
7
|
+
return child
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
class MeshAnalyzer
|
16
|
+
|
17
|
+
def self.validate_object(object)
|
18
|
+
Log3mf.context "validating geometry" do |l|
|
19
|
+
list = EdgeList.new
|
20
|
+
|
21
|
+
# if a triangle has a pid, then the object needs a pid
|
22
|
+
has_triangle_pid = false
|
23
|
+
|
24
|
+
mesh = find_child(object, "mesh")
|
25
|
+
if mesh
|
26
|
+
triangles = find_child(mesh, "triangles")
|
27
|
+
|
28
|
+
if triangles
|
29
|
+
triangles.children.each do |triangle|
|
30
|
+
v1 = triangle.attributes["v1"].to_s().to_i()
|
31
|
+
v2 = triangle.attributes["v2"].to_s().to_i()
|
32
|
+
v3 = triangle.attributes["v3"].to_s().to_i()
|
33
|
+
|
34
|
+
list.add_edge(v1, v2)
|
35
|
+
list.add_edge(v2, v3)
|
36
|
+
list.add_edge(v3, v1)
|
37
|
+
|
38
|
+
if not has_triangle_pid
|
39
|
+
has_triangle_pid = triangle.attributes["pid"] != nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
has_object_material = object.attributes["pid"] and object.attributes["pindex"]
|
44
|
+
if has_triangle_pid and not has_object_material
|
45
|
+
l.error :missing_object_pid
|
46
|
+
end
|
47
|
+
|
48
|
+
result = list.verify_edges()
|
49
|
+
if result == :bad_orientation
|
50
|
+
l.fatal_error :resource_3dmodel_orientation
|
51
|
+
elsif result == :hole
|
52
|
+
l.fatal_error :resource_3dmodel_hole
|
53
|
+
elsif result == :nonmanifold
|
54
|
+
l.fatal_error :resource_3dmodel_nonmanifold
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.validate(model_doc)
|
62
|
+
root = model_doc.root
|
63
|
+
node = root
|
64
|
+
|
65
|
+
if node.name == "model"
|
66
|
+
resources = find_child(node, "resources")
|
67
|
+
|
68
|
+
if resources
|
69
|
+
resources.children.each do |resource|
|
70
|
+
solid_model = resource.attributes["type"].to_s() == "model" or resource.attributes["type"].to_s() == "solidsupport"
|
71
|
+
if resource.name == "object" and solid_model
|
72
|
+
validate_object(resource)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/ruby3mf/model3mf.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require_relative 'mesh_analyzer'
|
2
|
+
|
1
3
|
class Model3mf
|
2
4
|
|
3
5
|
def self.parse(document, zip_entry)
|
@@ -5,10 +7,7 @@ class Model3mf
|
|
5
7
|
|
6
8
|
Log3mf.context "parsing model" do |l|
|
7
9
|
begin
|
8
|
-
model_doc =
|
9
|
-
config.strict.nonet.noblanks
|
10
|
-
end
|
11
|
-
l.info "We Found a Model, and it's XML!"
|
10
|
+
model_doc = GlobalXMLValidations.validate_parse(zip_entry)
|
12
11
|
rescue Nokogiri::XML::SyntaxError => e
|
13
12
|
l.fatal_error "Model file invalid XML. Exception #{e}"
|
14
13
|
end
|
@@ -25,7 +24,7 @@ class Model3mf
|
|
25
24
|
l.info "All model required resources are defined in .rels relationship files."
|
26
25
|
else
|
27
26
|
missing_resources.each { |mr|
|
28
|
-
l.error
|
27
|
+
l.error :model_resource_not_in_rels, mr: mr
|
29
28
|
}
|
30
29
|
end
|
31
30
|
|
@@ -40,13 +39,22 @@ class Model3mf
|
|
40
39
|
l.info "All model resource contenttypes are valid"
|
41
40
|
else
|
42
41
|
bad_types.each { |bt|
|
43
|
-
|
44
|
-
l.error "resource in model has invalid contenttype #{bt}", page: 10
|
42
|
+
l.error :resource_contentype_invalid, bt: bt
|
45
43
|
}
|
46
44
|
end
|
47
45
|
end
|
48
46
|
|
49
47
|
end
|
48
|
+
|
49
|
+
l.context "verifying model structure" do |l|
|
50
|
+
root = model_doc.root
|
51
|
+
l.error :root_3dmodel_element_not_model if root.name != "model"
|
52
|
+
|
53
|
+
children = model_doc.root.children.map { |child| child.name }
|
54
|
+
l.error :missing_model_children unless children.include?("resources") && children.include?("build")
|
55
|
+
end
|
56
|
+
|
57
|
+
MeshAnalyzer.validate(model_doc)
|
50
58
|
end
|
51
59
|
model_doc
|
52
60
|
end
|
@@ -5,9 +5,7 @@ class Relationships
|
|
5
5
|
Log3mf.context "parsing relationships" do |l|
|
6
6
|
begin
|
7
7
|
# Parse Relationships XML
|
8
|
-
doc =
|
9
|
-
config.strict.nonet.noblanks
|
10
|
-
end
|
8
|
+
doc = GlobalXMLValidations.validate_parse(zip_entry)
|
11
9
|
|
12
10
|
# Verify <Relationships><Relationship/></Relationships>
|
13
11
|
root_element = doc.children[0]
|
@@ -16,6 +14,11 @@ class Relationships
|
|
16
14
|
if relationship_elements.size > 0
|
17
15
|
relationship_elements.each do |node|
|
18
16
|
if node.is_a?(Nokogiri::XML::Element) && node.name == "Relationship"
|
17
|
+
relationships.each do |previous_rel|
|
18
|
+
if previous_rel[:target] == node['Target'] && previous_rel[:type] == node['Type']
|
19
|
+
l.error :multiple_relationships
|
20
|
+
end
|
21
|
+
end
|
19
22
|
relationships << {target: node['Target'], type: node['Type'], id: node['Id']}
|
20
23
|
l.info "adding relationship: #{relationships.last.inspect}"
|
21
24
|
else
|
@@ -30,8 +33,7 @@ class Relationships
|
|
30
33
|
start_part_type = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"
|
31
34
|
start_part_count = relationships.select { |r| r[:type] == start_part_type }.size
|
32
35
|
if start_part_count != 1
|
33
|
-
l.error
|
34
|
-
The correct attribute type should be \"#{start_part_type}\"", page: 10
|
36
|
+
l.error :invalid_startpart_type
|
35
37
|
end
|
36
38
|
end
|
37
39
|
end
|
data/lib/ruby3mf/texture3mf.rb
CHANGED
data/lib/ruby3mf/thumbnail3mf.rb
CHANGED
@@ -1,12 +1,18 @@
|
|
1
|
+
require 'mini_magick'
|
2
|
+
|
1
3
|
class Thumbnail3mf
|
2
4
|
|
3
5
|
def self.parse(doc, relationship_file)
|
4
6
|
img_type = MimeMagic.by_magic(relationship_file.get_input_stream)
|
7
|
+
img_colorspace = MiniMagick::Image.read(relationship_file.get_input_stream).colorspace
|
8
|
+
|
5
9
|
Log3mf.context "Thumbnail3mf" do |l|
|
6
10
|
l.fatal_error "thumbnail file must be valid image file", page: 10 unless img_type
|
11
|
+
l.fatal_error "CMYK JPEG images must not be used for the thumbnail", page: 36 if img_colorspace.include? "CMYK"
|
7
12
|
l.debug "thumbnail is of type: #{img_type}"
|
8
13
|
l.error "Expected a png or jpeg thumbnail but the thumbnail was of type #{img_type}", page: 12 unless ['image/png', 'image/jpeg'].include? img_type.type
|
9
14
|
end
|
10
15
|
|
11
16
|
end
|
17
|
+
|
12
18
|
end
|
data/lib/ruby3mf/version.rb
CHANGED
data/ruby3mf.gemspec
CHANGED
@@ -14,14 +14,6 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.homepage = "https://github.com/IPGPTP/ruby3mf"
|
15
15
|
spec.license = "MIT"
|
16
16
|
|
17
|
-
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
18
|
-
# delete this section to allow pushing this gem to any host.
|
19
|
-
# if spec.respond_to?(:metadata)
|
20
|
-
# spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
|
21
|
-
# else
|
22
|
-
# raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
23
|
-
# end
|
24
|
-
|
25
17
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
18
|
spec.bindir = "exe"
|
27
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
@@ -35,4 +27,5 @@ Gem::Specification.new do |spec|
|
|
35
27
|
spec.add_runtime_dependency 'rubyzip'
|
36
28
|
spec.add_runtime_dependency 'nokogiri', '~>1.6.8'
|
37
29
|
spec.add_runtime_dependency 'mimemagic'
|
30
|
+
spec.add_runtime_dependency 'mini_magick', '~> 4.6'
|
38
31
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby3mf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Whitmarsh, Jeff Porter, and William Hertling
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-01-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: mini_magick
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '4.6'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '4.6'
|
111
125
|
description: Read, write and validate 3MF files with Ruby easily.
|
112
126
|
email:
|
113
127
|
- mwhit@hp.com
|
@@ -133,7 +147,11 @@ files:
|
|
133
147
|
- lib/ruby3mf.rb
|
134
148
|
- lib/ruby3mf/content_types.rb
|
135
149
|
- lib/ruby3mf/document.rb
|
150
|
+
- lib/ruby3mf/edge_list.rb
|
151
|
+
- lib/ruby3mf/errors.yml
|
152
|
+
- lib/ruby3mf/global_xml_validations.rb
|
136
153
|
- lib/ruby3mf/log3mf.rb
|
154
|
+
- lib/ruby3mf/mesh_analyzer.rb
|
137
155
|
- lib/ruby3mf/model3mf.rb
|
138
156
|
- lib/ruby3mf/relationships.rb
|
139
157
|
- lib/ruby3mf/texture3mf.rb
|