ruby3mf 0.1.13 → 0.1.15
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/lib/ruby3mf.rb +1 -0
- data/lib/ruby3mf/document.rb +23 -9
- data/lib/ruby3mf/errors.yml +32 -8
- data/lib/ruby3mf/interpolation.rb +43 -0
- data/lib/ruby3mf/log3mf.rb +6 -13
- data/lib/ruby3mf/mesh_analyzer.rb +6 -0
- data/lib/ruby3mf/model3mf.rb +56 -0
- data/lib/ruby3mf/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49c3ed54c8588a2979b63822dbab8f3e339b5207
|
4
|
+
data.tar.gz: 50e088646a7159cf293fb896ce6ce7a5284b1131
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ecf6eaf61cbb478408212ef0741133b8c1d88d6891eaa6b330dc3102c752572da8b13f664f35067d5a167fbc25bc0333d07885a8dfb94ea29c6421a0f06e085
|
7
|
+
data.tar.gz: f220c8fc02162b03dbfbb2fa66b4e380f19e7a684ca0d59a5ab8cba6342333b00c35060802b9a2758f7109ff823792f62847e9b61ac6f606049ccd5c222db9fc
|
data/lib/ruby3mf.rb
CHANGED
data/lib/ruby3mf/document.rb
CHANGED
@@ -8,11 +8,18 @@ class Document
|
|
8
8
|
attr_accessor :objects
|
9
9
|
attr_accessor :zip_filename
|
10
10
|
|
11
|
+
# Relationship schemas
|
12
|
+
MODEL_TYPE = 'http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel'
|
13
|
+
THUMBNAIL_TYPE = 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail'
|
14
|
+
TEXTURE_TYPE = 'http://schemas.microsoft.com/3dmanufacturing/2013/01/3dtexture'
|
15
|
+
PRINT_TICKET_TYPE = 'http://schemas.microsoft.com/3dmanufacturing/2013/01/printticket'
|
16
|
+
|
11
17
|
# Relationship Type => Class validating relationship type
|
12
18
|
RELATIONSHIP_TYPES = {
|
13
|
-
|
14
|
-
|
15
|
-
|
19
|
+
MODEL_TYPE => {klass: 'Model3mf', collection: :models},
|
20
|
+
THUMBNAIL_TYPE => {klass: 'Thumbnail3mf', collection: :thumbnails},
|
21
|
+
TEXTURE_TYPE => {klass: 'Texture3mf', collection: :textures},
|
22
|
+
PRINT_TICKET_TYPE => {}
|
16
23
|
}
|
17
24
|
|
18
25
|
def initialize(zip_filename)
|
@@ -85,6 +92,11 @@ class Document
|
|
85
92
|
end
|
86
93
|
end
|
87
94
|
|
95
|
+
l.context "print tickets" do |l|
|
96
|
+
print_ticket_types = m.relationships.select { |rel| rel[:type] == PRINT_TICKET_TYPE }
|
97
|
+
l.error :multiple_print_tickets if print_ticket_types.size > 1
|
98
|
+
end
|
99
|
+
|
88
100
|
l.context "relationship elements" do |l|
|
89
101
|
# 3. Validate all relationships
|
90
102
|
m.relationships.each do |rel|
|
@@ -107,13 +119,15 @@ class Document
|
|
107
119
|
if relationship_file
|
108
120
|
relationship_type = RELATIONSHIP_TYPES[rel[:type]]
|
109
121
|
if relationship_type.nil?
|
110
|
-
l.
|
122
|
+
l.error :invalid_relationship_type, type: rel[:type]
|
111
123
|
else
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
124
|
+
unless relationship_type[:klass].nil?
|
125
|
+
m.send(relationship_type[:collection]) << {
|
126
|
+
rel_id: rel[:id],
|
127
|
+
target: rel[:target],
|
128
|
+
object: Object.const_get(relationship_type[:klass]).parse(m, relationship_file)
|
129
|
+
}
|
130
|
+
end
|
117
131
|
end
|
118
132
|
else
|
119
133
|
l.error "Relationship Target file #{rel[:target]} not found", page: 11
|
data/lib/ruby3mf/errors.yml
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
resource_contentype_invalid:
|
2
|
-
msg: "
|
2
|
+
msg: "Resource in model has invalid contenttype %{bt}"
|
3
3
|
page: 10
|
4
4
|
err_uri_empty_segment:
|
5
5
|
msg: 'No segment of a 3MF part name path may be empty'
|
@@ -49,6 +49,9 @@ invalid_content_type:
|
|
49
49
|
invalid_startpart_type:
|
50
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
51
|
page: 10
|
52
|
+
invalid_relationship_type:
|
53
|
+
msg: "Invalid relationship type '%{type}' specified in .rels relationship file. Parts in the 3D payload MUST use one of the appropriate relationship types to establish that relationship between two parts in the payload."
|
54
|
+
page: 10
|
52
55
|
invalid_thumbnail_colorspace:
|
53
56
|
msg: "CMYK JPEG images must not be used for the thumbnail"
|
54
57
|
page: 36
|
@@ -79,18 +82,39 @@ not_a_zip:
|
|
79
82
|
zero_size_texture:
|
80
83
|
msg: "Texture file must be valid image file"
|
81
84
|
page: 16
|
82
|
-
resource_contentype_invalid:
|
83
|
-
msg: "resource in model has invalid contenttype image/ping"
|
84
|
-
page: 10
|
85
85
|
has_xml_space_attribute:
|
86
|
-
msg: "
|
86
|
+
msg: "xml:space attribute is not allowed"
|
87
87
|
page: 16
|
88
|
+
invalid_model_unit:
|
89
|
+
msg: "Invalid unit value of '%{unit}' specified in model file."
|
90
|
+
page: 17
|
88
91
|
wrong_encoding:
|
89
|
-
msg: "
|
92
|
+
msg: "XML content must be UTF8 encoded"
|
90
93
|
page: 15
|
91
94
|
missing_object_pid:
|
92
|
-
msg: "
|
95
|
+
msg: "Object with triangle color override missing object level pid"
|
93
96
|
page: 26
|
94
97
|
missing_model_children:
|
95
|
-
msg: "
|
98
|
+
msg: "Model element must include resources and build as child elements"
|
96
99
|
page: 20
|
100
|
+
object_with_components_and_pid:
|
101
|
+
msg: "object with components and pid or pindex"
|
102
|
+
page: 26
|
103
|
+
build_with_other_item:
|
104
|
+
msg: "build item with object of type other"
|
105
|
+
page: 27
|
106
|
+
resource_id_collision:
|
107
|
+
msg: "resources must be unique within the model"
|
108
|
+
page: 22
|
109
|
+
metadata_elements_with_same_name:
|
110
|
+
msg: "metadata elements must not share the same name"
|
111
|
+
page: 22
|
112
|
+
multiple_print_tickets:
|
113
|
+
msg: "Only one print ticket allowed for any given model"
|
114
|
+
page: 13
|
115
|
+
unknown_required_extension:
|
116
|
+
msg: "Required extension not supported: %{ext}"
|
117
|
+
page: 14
|
118
|
+
missing_extension_namespace_uri:
|
119
|
+
msg: "Required extension '%{ns}' MUST refer to namespace with URI"
|
120
|
+
page: 14
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Interpolation
|
2
|
+
|
3
|
+
INTERPOLATION_PATTERN = Regexp.union(
|
4
|
+
/%%/,
|
5
|
+
/%\{(\w+)\}/, # matches placeholders like "%{foo}"
|
6
|
+
/%<(\w+)>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%<foo>.d"
|
7
|
+
)
|
8
|
+
|
9
|
+
def interpolate(string, values = {})
|
10
|
+
if values.keys == 0
|
11
|
+
string
|
12
|
+
else
|
13
|
+
string.gsub(INTERPOLATION_PATTERN) do |match|
|
14
|
+
if match == '%%'
|
15
|
+
'%'
|
16
|
+
else
|
17
|
+
key = ($1 || $2 || match.tr("%{}", "")).to_sym
|
18
|
+
value = values[key]
|
19
|
+
value = value.call(values) if value.respond_to?(:call)
|
20
|
+
$3 ? sprintf("%#{$3}", value) : value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def symbolize_recursive(hash)
|
27
|
+
{}.tap do |h|
|
28
|
+
hash.each { |key, value| h[key.to_sym] = map_value(value) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def map_value(thing)
|
33
|
+
case thing
|
34
|
+
when Hash
|
35
|
+
symbolize_recursive(thing)
|
36
|
+
when Array
|
37
|
+
thing.map { |v| map_value(v) }
|
38
|
+
else
|
39
|
+
thing
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
data/lib/ruby3mf/log3mf.rb
CHANGED
@@ -23,6 +23,7 @@ require 'yaml'
|
|
23
23
|
|
24
24
|
class Log3mf
|
25
25
|
include Singleton
|
26
|
+
include Interpolation
|
26
27
|
|
27
28
|
LOG_LEVELS = [:fatal_error, :error, :warning, :info, :debug]
|
28
29
|
|
@@ -63,28 +64,20 @@ class Log3mf
|
|
63
64
|
|
64
65
|
def method_missing(name, *args, &block)
|
65
66
|
if LOG_LEVELS.include? name.to_sym
|
66
|
-
#puts "***** #{name} called from #{caller[0]}"
|
67
67
|
log(name.to_sym, *args)
|
68
68
|
else
|
69
69
|
super
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
-
def log(severity, message, options={})
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
# puts "[#{@context_stack.join("/")}] #{severity.to_s.upcase} #{message}"
|
79
|
-
end
|
73
|
+
def log(severity, message, options = {})
|
74
|
+
error = @errormap.fetch(message.to_s) { {"msg" => message.to_s, "page" => nil } }
|
75
|
+
options[:page] = error["page"] unless options[:page]
|
76
|
+
message = interpolate(error["msg"], options)
|
77
|
+
@log_list << ["#{@context_stack.join("/")}", severity, message, options] unless severity==:debug && ENV['LOGDEBUG'].nil?
|
80
78
|
raise FatalError if severity == :fatal_error
|
81
79
|
end
|
82
80
|
|
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
|
-
|
88
81
|
def count_entries(*levels)
|
89
82
|
entries(*levels).count
|
90
83
|
end
|
@@ -15,6 +15,12 @@ end
|
|
15
15
|
class MeshAnalyzer
|
16
16
|
|
17
17
|
def self.validate_object(object)
|
18
|
+
Log3mf.context "verifying object" do |l|
|
19
|
+
children = object.children.map { |child| child.name }
|
20
|
+
have_override = object.attributes["pid"] or object.attributes["pindex"]
|
21
|
+
l.error :object_with_components_and_pid if have_override && children.include?("components")
|
22
|
+
end
|
23
|
+
|
18
24
|
Log3mf.context "validating geometry" do |l|
|
19
25
|
list = EdgeList.new
|
20
26
|
|
data/lib/ruby3mf/model3mf.rb
CHANGED
@@ -2,6 +2,13 @@ require_relative 'mesh_analyzer'
|
|
2
2
|
|
3
3
|
class Model3mf
|
4
4
|
|
5
|
+
VALID_UNITS = [ 'micron', 'millimeter', 'centimeter', 'meter', 'inch', 'foot' ].freeze
|
6
|
+
VALID_EXTENSIONS = {
|
7
|
+
'http://schemas.microsoft.com/3dmanufacturing/slice/2015/07' => {},
|
8
|
+
'http://schemas.microsoft.com/3dmanufacturing/material/2015/02' => {},
|
9
|
+
'http://schemas.microsoft.com/3dmanufacturing/production/2015/06' => {},
|
10
|
+
}.freeze
|
11
|
+
|
5
12
|
def self.parse(document, zip_entry)
|
6
13
|
model_doc = nil
|
7
14
|
|
@@ -12,6 +19,24 @@ class Model3mf
|
|
12
19
|
l.fatal_error "Model file invalid XML. Exception #{e}"
|
13
20
|
end
|
14
21
|
|
22
|
+
l.context "verifying requiredextensions" do |l|
|
23
|
+
required_extensions = model_doc.css("//model")[0]["requiredextensions"]
|
24
|
+
if required_extensions
|
25
|
+
required_extensions.split(" ").each do |ns|
|
26
|
+
namespace_uri = model_doc.namespaces["xmlns:#{ns}"]
|
27
|
+
if namespace_uri
|
28
|
+
if VALID_EXTENSIONS.has_key? namespace_uri
|
29
|
+
l.info "Found a valid required extension: #{namespace_uri}"
|
30
|
+
else
|
31
|
+
l.error :unknown_required_extension, ext: namespace_uri
|
32
|
+
end
|
33
|
+
else
|
34
|
+
l.error :missing_extension_namespace_uri, ns: ns
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
15
40
|
l.context "verifying 3D payload required resources" do |l|
|
16
41
|
# results = model_doc.css("model resources m:texture2d")
|
17
42
|
required_resources = model_doc.css("//model//resources//*[path]").collect { |n| n["path"] }
|
@@ -46,14 +71,45 @@ class Model3mf
|
|
46
71
|
|
47
72
|
end
|
48
73
|
|
74
|
+
l.context "verifying resources" do |l|
|
75
|
+
resources = find_child(model_doc.root, "resources")
|
76
|
+
if resources
|
77
|
+
ids = resources.children.map { |child| child.attributes["id"].to_s() if child.attributes["id"] }
|
78
|
+
l.error :resource_id_collision if ids.uniq.size != ids.size
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
49
82
|
l.context "verifying model structure" do |l|
|
50
83
|
root = model_doc.root
|
51
84
|
l.error :root_3dmodel_element_not_model if root.name != "model"
|
52
85
|
|
86
|
+
l.error(:invalid_model_unit, unit: root.attr('unit')) unless VALID_UNITS.include?(root.attr('unit'))
|
87
|
+
|
53
88
|
children = model_doc.root.children.map { |child| child.name }
|
54
89
|
l.error :missing_model_children unless children.include?("resources") && children.include?("build")
|
55
90
|
end
|
56
91
|
|
92
|
+
l.context "verifying build items" do |l|
|
93
|
+
build = find_child(model_doc.root, "build")
|
94
|
+
if build
|
95
|
+
items = build.children.map { |child| child.attributes["objectid"].to_s() if child.name == "item" }
|
96
|
+
|
97
|
+
resources = find_child(model_doc.root, "resources")
|
98
|
+
resources.children.each do |resource|
|
99
|
+
if resource.name == "object"
|
100
|
+
object_id = resource.attributes["id"].to_s()
|
101
|
+
l.error :build_with_other_item if resource.attributes["type"].to_s() == "other" and items.include?(object_id)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
l.context "checking metadata" do |l|
|
108
|
+
metadata = model_doc.root.css("metadata")
|
109
|
+
metadata_names = metadata.map { |met| met['name'] }
|
110
|
+
l.error :metadata_elements_with_same_name unless metadata_names.uniq!.nil?
|
111
|
+
end
|
112
|
+
|
57
113
|
MeshAnalyzer.validate(model_doc)
|
58
114
|
end
|
59
115
|
model_doc
|
data/lib/ruby3mf/version.rb
CHANGED
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.15
|
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: 2017-01-
|
11
|
+
date: 2017-01-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -150,6 +150,7 @@ files:
|
|
150
150
|
- lib/ruby3mf/edge_list.rb
|
151
151
|
- lib/ruby3mf/errors.yml
|
152
152
|
- lib/ruby3mf/global_xml_validations.rb
|
153
|
+
- lib/ruby3mf/interpolation.rb
|
153
154
|
- lib/ruby3mf/log3mf.rb
|
154
155
|
- lib/ruby3mf/mesh_analyzer.rb
|
155
156
|
- lib/ruby3mf/model3mf.rb
|